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.
What the component enforces, and what it only asks for
One required prop carries the entire argument of this page.
| Rule | Held by | How |
|---|---|---|
| An empty region says what was searched | The component | searched is required, so a region cannot claim there is nothing without saying where it looked.Asserted: EmptyState: proof of search is rendered |
| The four causes are not conflated | You | Nothing found, filtered to nothing, still loading and failed are four different states. The component renders the title and description you choose; picking the wrong one turns a failure into an absence. |
| A retry exists where the cause is a failure | You | action is whatever you pass, including nothing. |
| The scope described is the scope queried | You | “this site, all dates” is a string. If the query was narrower, the reader has been told something false in the most reassuring possible way. |
A rule stated on a component page reads as a rule the component keeps. 1 of these 4 are; 3 are obligations this page was quietly handing to the reader. This component makes it impossible to say nothing at all. It cannot make what you say accurate, and an inaccurate scope statement is worse than none.
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.
No prior studies
Searched this site, all dates · other sites not included
The scope is stated, so the reader knows what “none” is none of.
No prior studies
Searched all records
The query ran against this site only. The reader has been told something false in the most reassuring possible words, and will not order the outside films.
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). Which of these the component keeps and which are yours is stated in the guarantees table; a control the component does not keep is one your risk file must show you keep.
- Failure never renders as empty. (you) Mitigates: a system fault presented as clinical good news — the most consequential failure this component has.
- Proof of life required on clinical queues. (you) Mitigates: a dead pipeline reading as a quiet ward.
- Filtered is distinguished from empty, with counts. (you) Mitigates: hidden patients being indistinguishable from absent ones.
- Loading shows skeletons, never a claim of emptiness. (you) Mitigates: a transient state read as a settled one.
- Stale views escalate to Advisory. (you) Mitigates: a frozen screen trusted as current.
- No cached empty state after reconnect. (you) Mitigates: pre-outage emptiness presented as current.
- No congratulatory language. (you) Mitigates: reassurance the system has not earned.
Implementation
$ npx shadcn@latest add https://md.notjustany.tech/r/empty-state.json
<EmptyState
title= "No prior studies for this patient"
searched= "this site, all dates" // required: what was looked through
description= "Studies from other sites are not included in this search."
action={<Button variant= "outline" >Search other sites</Button>}
/>
// 'searched' is required, so an empty region cannot claim there is nothing
// without saying where it looked. Retry and clear-filter are actions you
// pass in, not behaviour the component owns.
What the component takes. This table used to list state, clinical, staleAfter — props the component does not have, and never had. Found by the registry gate’s props-table rule.
| Prop | Type | Default | Notes |
|---|---|---|---|
title | string | — | Required. What is empty. |
searched | string | — | Required. What was searched, over what period, from which sources — the proof that it looked. |
description | string | — | Why it may be empty, when that is known. |
action | React.ReactNode | — | A retry, or a way to widen the search. Yours to supply. |
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.