Components / surfaces

EmptyState

Listbox renders its own data-part="empty" — one line inside a popup. Same word, same idea, different scale; the anatomy wins on the attribute name, as it does for Rows and Menubar.

Rendered from the package · open on its own

Summary

What a container says when it holds nothing.

APG patternnone
Built onnative elements
Statusstable
Since0.2.0

A container, like Card: it centers and spaces what it is given and names none of it.

<EmptyState style={{ maxInlineSize: '36rem', marginInline: 'auto' }}>
  <Icon data-part="icon" path={ICONS.imageSquare} />
  <h2 data-se-text="heading">No projects yet</h2>
  <p data-se-text="body">Add a case study and it will show up here.</p>
  <Button data-part="action" intent="accent" emphasis="solid">Add project</Button>
</EmptyState>

Anatomy

A <div data-se="empty">. Everything inside is the caller's.

PartElementRequired
actionthe caller'sthe caller's
icon<span>no

Properties

PropertyValuesDefaultWhat it does
iconReact.ReactNodenoneA glyph above the words — an <Icon> at lg, which is the size the empty state's own rule gives it. Omit it: an empty state that says what to do next needs no picture, and a picture that repeats the sentence is decoration.
childrenReact.ReactNodenoneThe icon, the words and at most one action. There are four kinds of empty and they differ only in what these say — see the spec's Content section.

States

_None. Rest is the only condition it has._

Behavior

None.

Accessibility

It sets no live region, on purpose. A no-results state that replaces a list after a search has to announce, or a screen-reader reader waits on a page that already changed. A first-run state is the page as it arrived and must not. Only the caller knows which this is: put role="status" on the region that swaps, not on the empty state, so the announcement fires whether the result is a list or an empty.

The heading level is the caller's, for the same reason it is in Card.

The icon is decorative and takes aria-hidden. It repeats what the heading says.

Keyboard

None of its own. Anything inside it — usually one button — keeps its own keys.

Roles, states and properties

_None written. The elements carry their own._

Focus

Takes no focus. Its action, if it has one, is a normal tab stop in document order.

Screen reader

Not recorded as verified. Stage 4 of the readiness ladder — driving each component with VoiceOver and NVDA — is unstarted across the package, and no component should claim otherwise until docs/journal/ records the session that did it.

Contrast and target size

npm run check:contrast audits every color pair this component uses across all four skins, light and dark, and fails the build on a shortfall. Two waivers are on the record in docs/audit/DEFERRED.md.

It sets no target size of its own; anything interactive inside it brings its own.

Tested

  • test/browser/surfaces.spec.mjs

Known gaps

_No upstream requirement is recorded as not applicable._

Appearance

TokenUsed for
--se-icon-lgfont-size
--se-icon-smblock-size, font-size, inline-size
--se-space-2gap
--se-space-3margin-block-start
--se-space-5padding
--se-space-7padding
--se-text-mutedcolor
--se-text-placeholdercolor

It sets no measure. A line length depends on the column this sits in, which the component cannot see.

Motion

None. An empty state that fades in draws attention to an absence.

Content

There are four empty states and they are not interchangeable. Writing the wrong one is the most common mistake here.

KindWhat happenedWhat the words do
First runNothing has ever been addedExplain what this will hold, and offer the way to start
No resultsA search or filter matched nothingSay what was searched, and offer to clear it
ClearedThe reader emptied it deliberatelyConfirm, quietly. No call to action
FailedThe list could not loadSay what failed and offer to retry

Usage

A failed state is an Alert, or an empty state with an Alert inside it. The muted, quiet treatment reads as "nothing here" when the truth is "something broke".

When not to use it. For a list that is empty because it is still loading. That is a Skeleton, and showing an empty state first tells the reader something that is not yet true.

Notes

Listbox renders its own data-part="empty" — one line inside a popup. Same word, same idea, different scale; the anatomy wins on the attribute name, as it does for Rows and Menubar.