Empty state

Says why a space is empty and gives the one action that fills it.

Examples

First use

Agents0

No agents yet

Agents answer tickets and call tools.

No search results

Tickets0 of 412

No tickets match chargeback

Try a shorter word.

A load that failed

Reason

  • 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

  • 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

  • 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> default required

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)

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.