Empty state
Usage
Show an empty state where a list, table or dashboard tile has nothing to show, in the same place and at about the same height as the content it stands in for. Say what the situation is, what to do next, and offer one way to do it.
- First use — nothing has been created yet. Explain what will appear here and offer the create action: "No work orders yet" + "Create work order".
- No results — a search or filter matched nothing. Repeat the query, suggest a fix and offer "Clear filters". Keep the search box and filters on screen so the query can be edited.
- Cleared — the user finished the queue. Confirm it plainly ("All caught up") and say when there will be more. No action is required; a link to the next useful place is enough.
- Title states the situation in plain words; description is one sentence; one primary action at most. No jokes in error-adjacent states, no illustrations that push the action below the fold on a tablet.
- Not for failures: if the list could not load, use the error tone of the result panel with "Try again".
Anatomy
- Region —
.empty-state(reserves the height of the missing content) - Icon —
.result__icon, decorative (aria-hidden) - Title —
.result__title, a heading one level below the surrounding section - Guidance —
.result__description, one sentence - Actions —
.result__actions: one primary button, optional secondary link
Examples
Empty state
variant:first-use — inside a card
Work orders
No work orders yet
Repairs and services for this van appear here with their status and mechanic.
variant:no-results — filters stay editable
No results for ‘KX-99’
No vehicle at North Yard matches that plate. Check the number, or search all 6 depots.
variant:cleared — queue finished
Inspections due
All caught up
Every vehicle at North Yard has a valid inspection. The next one is due on .
<div class="empty-state">
<section class="result" aria-labelledby="wo-empty-t">
<span class="result__icon" aria-hidden="true"><svg><use href="#icon-clipboard-list"></use></svg></span>
<h2 class="result__title" id="wo-empty-t">No work orders yet</h2>
<p class="result__description">Repairs and services for this van appear here with their status and mechanic.</p>
<div class="result__actions">
<button type="button" class="btn btn--primary">Create work order</button>
<a class="btn btn--ghost" href="/work-orders/import">Import from spreadsheet</a>
</div>
</section>
</div>
<!-- No results: keep the search/filters above; announce the count in the search's role="status" node. -->
<!-- Cleared: .result--success, a link instead of a primary button. -->
API
| Hook | Values | Purpose |
|---|---|---|
.empty-state | block | Region that replaces the list/table; centres the result panel vertically |
--empty-state-min-block-size | length (default 20rem) | Match the height of the content it replaces |
.empty-state__toolbar | part | Search + filters row kept above a no-results state |
.result / .result--success | component | Icon, title, guidance and actions (see Result panel) |
Do and don't
Accessibility
| Situation | Behavior |
|---|---|
| Page loads empty | No live region; the heading is found by heading navigation |
| Filter or search returns nothing | The result count is announced politely ("No vehicles found") through the search's role="status"; focus stays in the search box |
| Last item in a queue is completed | Announce "All caught up" through a status message; move focus to the empty-state heading (tabindex="-1") only if the focused row was removed |
The icon is decorative; the title carries the meaning. The title is a real heading at the right level for its container (card title + 1).
Tokens
| Custom property | Purpose |
|---|---|
--empty-state-min-block-size | Reserved height (pattern property) |
--space-3, --space-4 | Toolbar gaps |
--color-feedback-info-*, --color-feedback-success-* | Icon disc via the result panel tones |