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.
Summary
What a container says when it holds nothing.
| APG pattern | none |
| Built on | native elements |
| Status | stable |
| Since | 0.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.
| Part | Element | Required |
|---|---|---|
action | the caller's | the caller's |
icon | <span> | no |
Properties
| Property | Values | Default | What it does |
|---|---|---|---|
icon | React.ReactNode | none | A 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. |
children | React.ReactNode | none | The 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
| Token | Used for |
|---|---|
--se-icon-lg | font-size |
--se-icon-sm | block-size, font-size, inline-size |
--se-space-2 | gap |
--se-space-3 | margin-block-start |
--se-space-5 | padding |
--se-space-7 | padding |
--se-text-muted | color |
--se-text-placeholder | color |
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.
| Kind | What happened | What the words do |
|---|---|---|
| First run | Nothing has ever been added | Explain what this will hold, and offer the way to start |
| No results | A search or filter matched nothing | Say what was searched, and offer to clear it |
| Cleared | The reader emptied it deliberately | Confirm, quietly. No call to action |
| Failed | The list could not load | Say 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.