Empty state
Says why a space is empty and gives the one action that fills it.
Examples
Agents0
No agents yet
Agents answer tickets and call tools.
Tickets0 of 412
No tickets match chargeback
Try a shorter word.
Failed
Tickets didn't load
The server returned 503.
ref_7c2e91f4a0b3Reason
No agents yet
Agents answer tickets and call tools.
emptydefault
No tickets match chargeback
Try a shorter word.
no-results
You can't see billing
Workspace owners manage billing.
no-access
- Failed
Tickets didn't load
The server returned 503.
error
Sizes
No agents yet
Agents answer tickets and call tools.
defaultdefault
No agents yet
Agents answer tickets and call tools.
sm
States
No keys
rest
No keys
hover
No keys
focus-visible
No tickets
retrying
Usage
When to use it
- Use
- A list, table or panel has no items and the person needs to learn how to add the first one.
- Use
- A search or filter matched nothing.
- Use
- A region could not load and has nothing else to show while it waits for a retry.
- Not for
- The content is on its way. Use Skeleton.
- Not for
- The page has a problem but its content still shows. Use Alert.
- Not for
- An action failed but the region keeps its content. Use Toast.
Rules
11 rules
- Must
- Render reason="error" with the cause when a request fails, never reason="empty" or "no-results". Because "No tickets yet" tells an operator the list is empty when the request failed, and only reason="error" sets role="alert".
- Must
- Use EmptyRetry with an onRetry that rejects on failure, not a plain Retry button. Because a plain button shows no pending state and no "Retry 2 failed at 14:03:12", so a dead endpoint looks like the button did nothing.
- Must
- Move focus to the loaded region heading or first row when a retry succeeds and the Empty unmounts. Because the focused Retry unmounts with the Empty and focus falls to the body.
- Should
- Give one action and at most one ghost; it is primary only in an empty view with no other primary (button-one-primary), else secondary. Because a page header already holding Create plus a primary in the empty state gives the view two ink fills.
- Should
- Name the thing and the reason in the title.
- Should
- Put a reference ID or status code in EmptyCode, not in the description.
- Must
- Add EmptyStatus or say "failed" in the title for reason="error". Because the fail hue alone vanishes in forced colors and for readers who cannot tell red.
- Must
- Put the empty state where the first row would be: top-left of the body, under the toolbar, as variant="plain". Because a centred block in a big box reads as a template and moves the action away from where the eye already is.
- Never
- Add an illustration or big icon; write a title, one muted line and the action. Because a 240px picture pushes the cause and the action out of a small panel.
- Never
- Write "Oops!" or "Looks like" copy; state the cause. Because "Oops" carries no cause, so the person cannot tell a retry from a support ticket.
- Should
- Write the query or filter that emptied the list in an EmptyQuery inside the title.
Accessibility
5 checks
- reason="error" sets role="alert" and announces the whole text once on mount.
- EmptyTitle renders an h3; the retry line is a polite status.
- EmptyStatus or the title says "failed"; the hue is not the only quartz.
- Tab moves to the first action; the region takes no focus. Enter or Space on Retry runs onRetry and blocks repeats.
- EmptyMedia is aria-hidden.
Edge cases
10 edge cases
- Long title and reference ID
- The title wraps; the ID breaks anywhere inside the column.
- Right to left
- Sits top-right, where the first row starts.
- Inside a card or table
- Use variant="plain"; it shares that frame and never brings its own.
- 120px wide
- Actions wrap one per line; no sideways scroll.
- Retry never returns
- Stays pending with its box held; the caller adds a timeout that rejects.
- Retry fails again
- The retry line updates with the count and time in a status region.
- Ten fast clicks on Retry
- One attempt starts; pending blocks the rest.
- No children
- Renders an empty frame; always include EmptyTitle.
- Forced colors
- The frame line survives; the status word still says "Failed".
- Reduced motion
- The Retry spinner stops.
API
6 props
reason'empty' | 'no-results' | 'no-access' | 'error'default'empty'status'fail' | 'warn'default'fail'variant'framed' | 'plain'default'framed'size'default' | 'sm'default'default'align'center' | 'start'default'start'EmptyRetry onRetry() => Promise<unknown>defaultrequired
Tokens
19 tokens
--surface-page- var(--paper)
--line-frame- oklch(var(--ink-c) / var(--line-frame-alpha))
--line-control- oklch(var(--ink-c) / 0.16)
--fail-line- oklch(60% 0.236 26)
--warn-line- oklch(62% 0.132 68)
--accent-mark- var(--ink)
--radius-inner- 4px
--radius-frame- 10px
--text-3- oklch(var(--ink-c) / 0.66)
--fail-text- oklch(51% 0.180 26)
--type-state-word- 400 14px/16px var(--font-sans)
--type-heading-xs- 400 16px/20px var(--font-sans)
--type-label- 400 14px/16px var(--font-sans)
--type-body-sm- 380 13px/18px var(--font-sans)
--text-1- var(--ink)
--text-2- oklch(var(--ink-c) / 0.80)
--type-tag- var(--type-code-sm)
--measure-short- 48ch
--type-label-sm- 400 12px/16px var(--font-sans)