Empty state
Nothing on screen is the most ambiguous thing an interface can show. In clinical software it has at least four causes, three of which are problems — so an empty state's job is to say which.
Overview
In consumer software an empty state is a chance to be charming. Here it is a diagnostic surface, because "no cases awaiting review" and "the ECG feed died an hour ago" produce identical screens unless the component distinguishes them — and a clinician reading the first when the second is true will stop looking.
The rule underneath everything on this page: an empty state must prove why it is empty. A timestamp and a source count do more for safety than any illustration.
Diagnostic
Names its cause. Genuinely empty, filtered, still loading and failed are four different states and never share a rendering.
Evidenced
A healthy empty state carries proof of life — when the system last checked, and how many sources it is watching.
Plain
No illustrations, no encouragement, no personality. A clinician deciding whether to trust an empty queue needs facts, not reassurance.
The four empty states
| Cause | Severity | Must say |
|---|---|---|
| Genuinely empty | Informational | The state, plus proof of life — last check, sources monitored |
| Filtered | Advisory | How many rows exist, what is hiding them, and a one-action reset |
| Loading | — | Skeleton rows. Never a blank region, never a message claiming emptiness |
| Failed | Urgent | That data could not be loaded, which sources are affected, and what to do meanwhile |
If the system could not load the data, the region does not render an empty state at all — it renders an Alert Banner at Urgent. Empty means "we looked and there is nothing". Failed means "we do not know". Collapsing the second into the first is the single most dangerous thing this component can do, because it converts a system fault into apparent clinical good news.
Anatomy
| Element | Rule |
|---|---|
| Title | States the fact, not a feeling. "No cases awaiting review", never "All caught up". |
| Description | What will change this state, in one sentence. |
| Proof of life | Required on any clinical queue. Last successful check, sources healthy versus total, and freshness of the view. |
| Action | Only where one exists — clearing a filter, retrying. Never an invented one. |
The other three
| Patient | Finding | Waiting |
|---|---|---|
Note that only the first is an empty state at all. Filtered renders an Advisory, failed renders an Urgent, and loading renders skeleton rows that reserve the same space the real rows will occupy — see Grid & layout.
Writing it
- State the fact. "No cases awaiting review". Never "All caught up", "Nothing to see here", "You're all done" — see Voice & tone.
- Never congratulate. An empty queue is not an achievement, and it may be a fault.
- No illustrations or icons. They add nothing a clinician needs and cost vertical space that the proof-of-life line uses better.
- Give the numerator and denominator. "4 of 4 sources" is checkable; "monitoring sources" is not.
- Absolute times, not relative, for the last check — "14:47:12", not "a few minutes ago". Freshness of the view may be relative.
Empty and demonstrably alive. A clinician can tell the difference between quiet and broken without leaving the screen.
Congratulates the clinician on a state that may be a dead ingestion pipeline. Identical rendering whether the ward is quiet or the feed died an hour ago.
Freshness
An empty state is a claim about now, and it decays. Three rules:
- Re-render on every poll, so the proof-of-life line advances visibly even when nothing else changes. A frozen timestamp is itself the signal that something is wrong.
- Beyond a documented staleness threshold the state escalates — the empty state is replaced by an Advisory saying the view has not refreshed, with the age.
- Never show a cached empty state after a reconnect until a fresh poll has succeeded; show loading instead.
Do's and don'ts
Empty and demonstrably alive. Quiet ward and dead pipeline are distinguishable without leaving the screen.
No timestamp, no source count. Identical whether the ward is quiet or the ECG feed died an hour ago.
Accessibility
- Announced politely on transition to empty, so a screen-reader user knows the region changed rather than discovering it silent.
- The proof-of-life line is real text, in the reading order, never a tooltip or a hover.
- Skeletons are
aria-hiddenwith the region markedaria-busy="true", so assistive technology hears "loading", not a row of blanks. - Never the sole occupant of a landmark — the region keeps its heading so it is still navigable when empty.
- Contrast. The proof-of-life line is muted but meets AA; it is information, not decoration.
Outcomes of use
What this contributes to, in the terms of Usability & context of use. These are attributes believed to contribute to an outcome; the outcome itself is settled by observing real use in a specified context, not by this page.
- Effectiveness — completeness, directly. A clinician correctly concludes "there is nothing to review" only when that is true, and detects the cases where it is not.
- Efficiency — no need to leave the screen to verify the system is running, which is otherwise the only way to check.
- Satisfaction — calibrated trust in the queue. Quiet that has been proven is restful; quiet that might be a fault is not.
Clinical safety notes
Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1).
- Failure never renders as empty. Mitigates: a system fault presented as clinical good news — the most consequential failure this component has.
- Proof of life required on clinical queues. Mitigates: a dead pipeline reading as a quiet ward.
- Filtered is distinguished from empty, with counts. Mitigates: hidden patients being indistinguishable from absent ones.
- Loading shows skeletons, never a claim of emptiness. Mitigates: a transient state read as a settled one.
- Stale views escalate to Advisory. Mitigates: a frozen screen trusted as current.
- No cached empty state after reconnect. Mitigates: pre-outage emptiness presented as current.
- No congratulatory language. Mitigates: reassurance the system has not earned.
Implementation
$ npx shadcn@latest add https://md.notjustany.tech/r/empty-state.json
// The four causes are a discriminated union, so "empty" cannot be
// rendered without the evidence that justifies it.
type RegionState =
| { kind: "empty"; lastCheck: string; sourcesOk: number; sourcesTotal: number }
| { kind: "filtered"; total: number; hidden: number; filterLabel: string }
| { kind: "loading" }
| { kind: "failed"; affected: string[]; lastSuccess: string }
<EmptyState state={state} onClearFilter={clear} onRetry={retry} />
// failed renders an AlertBanner at urgent, never an empty state.
// loading renders skeleton rows sized to the real rows.
| Prop | Type | Notes |
|---|---|---|
state | RegionState |
Required. empty cannot be constructed without
lastCheck and the source counts. |
clinical | boolean |
Default true. Enforces proof of life; set false only for non-clinical
regions such as an empty settings list. |
staleAfter | ISO 8601 duration |
Beyond this, the empty state escalates to an Advisory. |
Related
- Data grid — the most common host.
- Triage worklist — this rule in context.
- Alert Banner — what failure renders instead.
- Voice & tone — why "All caught up" is banned.