Empty states
Three shapes for a surface with nothing in it — and why a blank page is indistinguishable from a broken one.
A blank screen cannot be told apart from a failed one. The user retries, then reports a bug. Every empty surface takes one of three shapes, chosen by what the surface is, and none of them is an apology.
| Surface | Shape |
|---|---|
| A screen the user can act on | The filled card |
| A register, a table, a filtered list | One sentence in the frame |
| A screen made of figures | The blurred sample |
The filled card
An illustration, a title, one sentence of explanation, the primary action, and a strip of steps saying what the screen does once it has content.
<EmptyState
image={{ src: "/brand/illustration.svg", alt: "…" }}
title="No applications in progress"
description="Nothing has reached your scope yet. As soon as one is filed it appears here with its status."
action={<Button>Register an application</Button>}
features={[
{ icon: <FilePlusIcon />, title: "Register", description: "Capture the applicant and the subject." },
{ icon: <PaperclipIcon />, title: "Attach files", description: "Add the required supporting documents." },
{ icon: <ArrowRightIcon />, title: "Forward", description: "It then enters the review circuit." },
]}
/>The steps strip is the difference between a shrug and a pattern. Without it the card says there is nothing; with it the card says this is what the page is for, and this is how it fills up.
Illustration before icon. Where the product has its own illustrations, use one — see iconography. A stock glyph filling a large empty area reads as a placeholder somebody never came back to. Fall back to an icon in a tinted tile only when no illustration exists.
One primary action, at most one secondary. An empty state offering four choices is a menu.
The action is the same action as the page header's, not a second wording of it. Two names for one command teach the reader that they differ.
One sentence in the frame
Inside a table or a filtered list the card is too much furniture. One sentence, occupying the space the rows would have, saying what would be there and why it is not.
<TableEmpty>
No invitation issued. Every account other than the first is born here.
</TableEmpty>Say the cause, never the absence. "No results" is a fact already visible on screen. "No entry matches this filter" tells the reader what to change.
The blurred sample
A dashboard with no data has nothing true to say, and a grid of zeros is worse than empty because it looks like a measurement. Feed the real components an example dataset, blur them, and put an unambiguous message on top.
const empty = view.filed === 0 && view.decided === 0
<div className="relative">
<div aria-hidden="true" className="pointer-events-none select-none blur-sm">
<Overview view={SAMPLE} />
</div>
<div className="absolute inset-0 grid place-items-center">
<Card>
<h2>No data yet</h2>
<p>The figures behind are examples: they show the shape this page will take.</p>
<Button>Register the first one</Button>
</Card>
</div>
</div>The same components, never a copy. A mock-up with its own markup drifts from the screen it announces at the first change to either.
Say in the message that the numbers are examples. A blurred figure is still a figure and somebody will read it. That sentence is the only thing standing between an example and a lie.
The sample is decoration: aria-hidden and pointer-events-none. It must not
be reachable by keyboard, by screen reader, or by a stray click.
Rules across all three
Never a decorative zero. A tile reading 0 teaches nothing. Drop the tile, or
say the thing that is actually true — "not recorded".
No gradient, no glow. The card is a surface with a hairline. Where the design system bans gradients, the empty state is not an exception — it is where they creep in first, because the surface looks bare and somebody reaches for a fill.
Copy in an empty state ages like any other copy. A steps strip still describing a workflow the product changed is a defect; it belongs in review.
How it fails
| Failure | What it costs |
|---|---|
| The blank page | Indistinguishable from a failed load |
| "Oops! Nothing here yet 🤷" | A line spent, nothing said, and it ages badly |
| Zeros instead of a sample | Reads as a measurement rather than as absence |
| An empty state nobody re-reads | Describes a workflow that no longer exists |