Empty state

Every list, table and dashboard designs its empty state (UX guide §7.2). Three kinds: first use ("Noch keine Lektionspläne." + "Plan erstellen"), no results ("Keine Mitglieder für «Mueler»." + "Filter zurücksetzen") and all done ("Keine offenen Anmeldungen. Gut gemacht.", usually without action).

Anatomy (design guide §9.3): a 24 px icon in a 48 px --color-surface-2 circle, a short title saying what is (not) there, one sentence explaining why or what to do, and at most one action (Button data, often a link via href). Centred in its container, max --readable-max wide.

Set live: true when the empty state appears as the result of the user's own search or filter, so screen readers hear "Keine Mitglieder gefunden" without moving focus.

Examples

Nothing today (with link)

Heute keine Trainings

Du bist für heute für kein Training eingetragen.

Ganze Woche ansehen
Data
render({
  "icon": "calendar",
  "title": "Heute keine Trainings",
  "text": "Du bist für heute für kein Training eingetragen.",
  "action": {
    "label": "Ganze Woche ansehen",
    "href": "#woche"
  }
})
Markup
<div class="empty"><span class="empty__icon"><svg class="icon" data-size="lg" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#calendar"/></svg></span><p class="empty__title">Heute keine Trainings</p><p>Du bist für heute für kein Training eingetragen.</p><a class="button" href="#woche">Ganze Woche ansehen</a></div>

First use

Noch keine Lektionspläne

Erstelle deinen ersten Plan oder übernimm einen Vorschlag.

Data
render({
  "icon": "list",
  "title": "Noch keine Lektionspläne",
  "text": "Erstelle deinen ersten Plan oder übernimm einen Vorschlag.",
  "action": {
    "label": "Plan erstellen",
    "variant": "primary",
    "icon": "plus"
  }
})
Markup
<div class="empty"><span class="empty__icon"><svg class="icon" data-size="lg" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#list"/></svg></span><p class="empty__title">Noch keine Lektionspläne</p><p>Erstelle deinen ersten Plan oder übernimm einen Vorschlag.</p><button class="button" type="button" data-variant="primary"><svg class="icon" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#plus"/></svg>Plan erstellen</button></div>

No results

Keine Mitglieder für «Mueler»

Prüfe die Schreibweise oder entferne Filter.

Data
render({
  "icon": "search",
  "title": "Keine Mitglieder für «Mueler»",
  "text": "Prüfe die Schreibweise oder entferne Filter.",
  "action": {
    "label": "Filter zurücksetzen"
  },
  "live": true
})
Markup
<div class="empty" role="status"><span class="empty__icon"><svg class="icon" data-size="lg" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#search"/></svg></span><p class="empty__title">Keine Mitglieder für «Mueler»</p><p>Prüfe die Schreibweise oder entferne Filter.</p><button class="button" type="button">Filter zurücksetzen</button></div>

All done

Keine offenen Anmeldungen

Gut gemacht. Neue Anmeldungen erscheinen hier.

Data
render({
  "icon": "check-circle",
  "title": "Keine offenen Anmeldungen",
  "text": "Gut gemacht. Neue Anmeldungen erscheinen hier."
})
Markup
<div class="empty"><span class="empty__icon"><svg class="icon" data-size="lg" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check-circle"/></svg></span><p class="empty__title">Keine offenen Anmeldungen</p><p>Gut gemacht. Neue Anmeldungen erscheinen hier.</p></div>

Data contract

What render(data) accepts — validated against this JSON Schema in development and tests. Unknown fields are rejected. The same schema is in components.json.

FieldTypeRequiredDefaultDescription
iconstringoptional"info"Icon id from the sprite that fits the content ("calendar", "users", "search", "check-circle").
titlestringrequired—What is (not) there: "Heute keine Trainings".
min 1 chars
headingLevelintegeroptional—Render the title as h2–h4 when the empty state replaces a whole section; otherwise it is a p.
≥ 2 · ≤ 4
textstringoptional—One sentence: why it is empty or what to do next.
actionobjectoptional—At most one action (Button data), e.g. { label: "Plan erstellen", variant: "primary" } or a link with href.
action.labelstringoptional—Visible text. Required unless iconOnly.
min 1 chars
action.variant"secondary" | "primary" | "ghost" | "danger" | "danger-ghost"optional"secondary"Visual weight / meaning.
action.size"sm" | "md" | "lg"optional"md"sm (36 px) only in dense desktop tables; lg (52 px) for the single main action of a screen.
action.iconstringoptional—Icon id from the sprite, shown before the label.
action.iconOnlybooleanoptionalfalseSquare button showing only icon. Requires ariaLabel.
action.ariaLabelstringoptional—Accessible name when the visible label is missing or ambiguous ("Weitere Aktionen für Lea Müller").
action.hrefstringoptional—Render an <a> that looks like a button (navigation, not actions).
action.type"button" | "submit" | "reset"optional"button"Button type inside forms.
action.valuestringoptional—Form value, e.g. cancel / confirm inside <form method="dialog">.
action.namestringoptional—Form field name when the button submits a value.
action.disabledbooleanoptionalfalseNot available now. Prefer explaining why nearby over silently disabling.
action.busybooleanoptionalfalseAction in progress (aria-busy).
action.formNoValidatebooleanoptionalfalseSubmit without native validation — use on "Abbrechen" inside <form method="dialog"> so required fields do not block closing.
action.block"always" | "mobile"optional—Full width always, or only below 768 px.
action.behaviorstringoptional—Behaviour hook (data-behavior), e.g. open-dialog, theme-toggle.
action.actionstringoptional—Screen action name (data-action). Declared in the actions of the screen with a handler and a risk class; the engine adds undo or confirm accordingly.
pattern ^[a-z][a-zA-Z0-9-]*$
action.actionIdstringoptional—Id of the thing the action applies to (data-id), when the button is not inside an element that has one.
pattern ^[A-Za-z0-9_-]+$
action.targetstringoptional—Id of the element a behaviour acts on (data-target), e.g. the dialog to open.
action.popoverTargetstringoptional—Id of a popover menu this button toggles (popovertarget).
livebooleanoptionalfalseAnnounce politely (role="status") — for empty search/filter results.
JSON Schema
{
  "type": "object",
  "additionalProperties": false,
  "required": [
    "title"
  ],
  "properties": {
    "icon": {
      "type": "string",
      "default": "info",
      "description": "Icon id from the sprite that fits the content (\"calendar\", \"users\", \"search\", \"check-circle\")."
    },
    "title": {
      "type": "string",
      "minLength": 1,
      "description": "What is (not) there: \"Heute keine Trainings\"."
    },
    "headingLevel": {
      "type": "integer",
      "minimum": 2,
      "maximum": 4,
      "description": "Render the title as `h2`–`h4` when the empty state replaces a whole section; otherwise it is a `p`."
    },
    "text": {
      "type": "string",
      "description": "One sentence: why it is empty or what to do next."
    },
    "action": {
      "type": "object",
      "additionalProperties": false,
      "required": [],
      "properties": {
        "label": {
          "type": "string",
          "minLength": 1,
          "description": "Visible text. Required unless `iconOnly`."
        },
        "variant": {
          "type": "string",
          "enum": [
            "secondary",
            "primary",
            "ghost",
            "danger",
            "danger-ghost"
          ],
          "default": "secondary",
          "description": "Visual weight / meaning."
        },
        "size": {
          "type": "string",
          "enum": [
            "sm",
            "md",
            "lg"
          ],
          "default": "md",
          "description": "`sm` (36 px) only in dense desktop tables; `lg` (52 px) for the single main action of a screen."
        },
        "icon": {
          "type": "string",
          "description": "Icon id from the sprite, shown before the label."
        },
        "iconOnly": {
          "type": "boolean",
          "default": false,
          "description": "Square button showing only `icon`. Requires `ariaLabel`."
        },
        "ariaLabel": {
          "type": "string",
          "description": "Accessible name when the visible label is missing or ambiguous (\"Weitere Aktionen für Lea Müller\")."
        },
        "href": {
          "type": "string",
          "description": "Render an `<a>` that looks like a button (navigation, not actions)."
        },
        "type": {
          "type": "string",
          "enum": [
            "button",
            "submit",
            "reset"
          ],
          "default": "button",
          "description": "Button type inside forms."
        },
        "value": {
          "type": "string",
          "description": "Form value, e.g. `cancel` / `confirm` inside `<form method=\"dialog\">`."
        },
        "name": {
          "type": "string",
          "description": "Form field name when the button submits a value."
        },
        "disabled": {
          "type": "boolean",
          "default": false,
          "description": "Not available now. Prefer explaining why nearby over silently disabling."
        },
        "busy": {
          "type": "boolean",
          "default": false,
          "description": "Action in progress (`aria-busy`)."
        },
        "formNoValidate": {
          "type": "boolean",
          "default": false,
          "description": "Submit without native validation — use on \"Abbrechen\" inside `<form method=\"dialog\">` so required fields do not block closing."
        },
        "block": {
          "type": "string",
          "enum": [
            "always",
            "mobile"
          ],
          "description": "Full width always, or only below 768 px."
        },
        "behavior": {
          "type": "string",
          "description": "Behaviour hook (`data-behavior`), e.g. `open-dialog`, `theme-toggle`."
        },
        "action": {
          "type": "string",
          "pattern": "^[a-z][a-zA-Z0-9-]*$",
          "description": "Screen action name (`data-action`). Declared in the `actions` of the screen with a handler and a risk class; the engine adds undo or confirm accordingly."
        },
        "actionId": {
          "type": "string",
          "pattern": "^[A-Za-z0-9_-]+$",
          "description": "Id of the thing the action applies to (`data-id`), when the button is not inside an element that has one."
        },
        "target": {
          "type": "string",
          "description": "Id of the element a behaviour acts on (`data-target`), e.g. the dialog to open."
        },
        "popoverTarget": {
          "type": "string",
          "description": "Id of a popover menu this button toggles (`popovertarget`)."
        }
      },
      "description": "At most one action (Button data), e.g. `{ label: \"Plan erstellen\", variant: \"primary\" }` or a link with `href`."
    },
    "live": {
      "type": "boolean",
      "default": false,
      "description": "Announce politely (`role=\"status\"`) — for empty search/filter results."
    }
  }
}

Markup & states

Root: div.empty — usable without render() by writing the markup directly.

Attributes

AttributeValuesDefaultDescription
role="status"——Polite announcement (with live).

Parts

PartDescription
.empty__icon48 px --color-surface-2 circle holding the 24 px icon in --color-text-muted (decorative).
.empty__titleTitle (p or h2–h4), --text-lg semibold.
pOne-sentence explanation in --color-text-muted.
.buttonOptional single action.

Accessibility

  • The icon is decorative (aria-hidden); title and text carry the message.
  • With live, the region is a polite status so filter results are announced without stealing focus.
  • Only one action, so keyboard users find the next step immediately.

Guidance

DoSay why it is empty and what to do next.
DoFor "no results", repeat the search term and offer to reset filters.
DoFor "all done", tell what comes next ("Nächstes Training: Mo 17:30").
Don'tNo illustrations or emoji.
Don'tDon't show an empty table with headers only.
Don'tDon't blame the user ("Du hast nichts eingegeben").

Related: Skeleton · List · Table · Button