Empty

What a list looks like when it has nothing in it: a drawing, a sentence saying why, and usually one thing to do about it.

Accessibility

Empty is a div with a drawing, a sentence and a footer. It has no roles and no keyboard behavior to get right, so the work is in the sentence, in announcing the swap, and in its two muted parts.

What it renders

Empty is a plain <div> with up to three children: an image wrapper, a description and a footer. There is no role, no landmark, no live region and no focus management. Anything else you pass is spread onto the root, so an aria-live or a role you add lands where you expect it.
  • The illustration is not hidden. Both presets are inline SVG and both carry a <title> element holding the locale's own description string (“No data” in English), which gives the drawing an accessible name. Beside a description that says the same thing, a screen reader reads the message twice.
  • A string image becomes an <img>. Its alt is the description when that is a string, and the word empty when it is a node or false. A rich description therefore leaves an alt of “empty”, the one case worth checking.
  • A node image is passed straight through. Nothing is added to it: an <Icon> or an SVG of your own keeps whatever semantics it arrived with, so decorative ones need their own aria-hidden.
  • description={false} removes the element. The whole message is then whatever the illustration's <title> happens to say, which is the untranslated default unless the locale was changed.

The description carries the message

For a reader who is not looking at the drawing, the description is the empty state. It is the only part that is guaranteed to be read, and it is read out of context, without the card title or the table header above it.
  • Name the thing that is missing, so the sentence stands alone: “No invoices yet” survives being read on its own where “No data” does not.
  • Keep it text. A description built out of icons and badges reads as a list of names with no sentence in it, and it also demotes the <img> alt to the literal “empty”.
  • Do not lean on the illustration to carry a distinction. The default and the simple preset differ only in size and drawing; their accessible name is the same locale string either way.
  • Where the drawing is purely decorative beside a description that already says everything, the presets cause a duplicate reading. A node image of your own, marked aria-hidden, avoids it.

Announcing that a filter matched nothing

The common failure is not the component, it is the swap. A reader types into a search field, the rows are replaced by an Empty, and nothing is announced: the focus is still in the input, and a plain div appearing elsewhere in the document is silent.
  • Announce, do not move focus. Pulling focus to the empty state takes the cursor out of the field the reader is still typing in, and loses the caret position. Let the region speak instead.
  • Put the live region on the container, not on the Empty. A role="status" (or aria-live="polite") wrapper that is in the DOM before the results change is what gets announced. An element that appears carrying its own aria-live is frequently missed, because the region was not there to be watched.
  • Polite, never assertive. A result count that changes on every keystroke and interrupts each time is worse than silence. If the query is debounced for painting, debounce the announcement with it.
  • Announce the same sentence the description shows, so the reader who hears it and the reader who sees it are told the same thing.

Naming the action

children lands in the footer, and it is the only focusable thing an Empty normally contains. It is reached by Tab from whatever precedes the container, in document order, with no help from this component.
  • The button's own label has to work alone: “Create invoice”, not “Create” leaning on the description above it. A tab list of one-word buttons is not navigable.
  • An icon-only action needs its word kept and hidden with sr-only. In a footer this is almost never worth it: there is room for the word.
  • Where the action needs the description for context, point aria-describedby at the description element rather than padding the button's own name.
  • Keep it to one. Two focusable children inside a placeholder give the reader a decision in a place that exists to report the absence of one.

Contrast, in both modes

Two parts of Empty are muted, and muted is where contrast goes wrong.
  • The description is muted by default. It uses the theme's description color, which passes on the default background. On a tinted card or a custom background, check it again: this is body text and needs 4.5:1.
  • The illustration follows the theme. The presets take their colors from the theme in both modes. A hardcoded color, a flat PNG or a single-color SVG passed to image does not, and can disappear in dark mode.
  • The drawing is decoration in both modes: it never carries information the description does not. Keep it that way and its contrast stops being a compliance question.
  • An action in the footer is a real control and owes the usual 3:1 on its own boundary, against the container it sits on rather than against the page.