Empty and first use
The surface before there is anything to show. There are six kinds of nothing, and rendering the wrong one is a safety problem rather than a polish problem.
When to use
Use this pattern whenever a health surface has to render and there is no value to put on it.
In most products an empty state is a polish concern. In a health product it is a correctness concern, because there are six distinct kinds of nothing and they mean opposite things:
| Kind of nothing | What it means | What the reader must not conclude |
|---|---|---|
| Never collected | The reader has not started | That there is nothing to worry about |
| Collected, none yet in range | The filter is empty, the data is not | That they stopped logging |
| Not yet arrived | A result is pending | That it is late or lost |
| Failed to load | The app does not know | That the value is zero or unchanged |
| Withheld | The reader revoked access | That the data was deleted |
| Genuinely zero | The measured value is zero | That the reading is missing |
A blank tile, a dash, and a "0" are three different assertions, and a product that uses one glyph for all six is telling readers things it does not know. The last row is the one that catches teams out: "0 steps yesterday" and "we have no step data for yesterday" look identical in most dashboards and are not the same fact.
When not to use
- The data exists and is old. That is not empty, it is stale, and it has its own honesty requirements. Use Offline and stale data.
- The reader has never opened the product. The first-open sequence is Onboarding and first run. This pattern picks up afterwards, on individual surfaces.
- The value is below a minimum window. A trend with four points is not empty. It has data and cannot yet support a direction. Use Trend review.
- You want the definitions. The five canonical data states are empty, loading, error, stale and partial. They are defined once in Data states and every data surface uses those definitions.
How it works
flowchart TD
A["Surface renders"] --> B{"Do we have a value?"}
B -->|"yes"| C["Render it"]
B -->|"no"| D{"Do we know why not?"}
D -->|"no"| E["Error state: say we could not load it, offer retry"]
D -->|"yes"| F{"Which reason?"}
F -->|"nothing collected"| G["First-use: one action, one sentence of value"]
F -->|"nothing in this filter"| H["Say the filter is empty and offer to widen it"]
F -->|"pending"| I["Say what is expected and by when"]
F -->|"access withdrawn"| J["Say access is off and how to turn it back on"]
F -->|"measured zero"| K["Render zero as a value, with its unit"]NOT IMPLEMENTED. This component does not exist in any released version of opsinjs. There is no package to install, no module to import and no props interface to generate code against. Everything on this page is a specification of intended behaviour and may change without notice. Do not write code against it.
This component is not built yet
PlannedRoadmapWhat “planned” means
Rules:
- Never render an absent value as a number. No zeroes, no dashes styled like
data, no last-known value dressed as current. If the shape of the layout needs
filling, use a
Skeletonwhile loading and anEmptyStateafterwards. - A first-use state has exactly one action. The surface exists to get the reader to one place; a screen of equal-weight suggestions is a menu, not an empty state.
- Say what would fill it. "Your readings will appear here once you log one" is useful. "No data" is not.
- Never apply a clinical status to absence. An empty tile is not
attentionand notsteady. It has no status, because there is nothing to have a status about. This is the most common way the status axis leaks. The two colour axes is the rule it breaks. - An error is not an empty state. If the request failed, say so and offer a retry. Presenting a failure as "no data yet" makes the reader believe a fact about themselves that is actually a fact about your network.
- Loading has a floor and a ceiling. Do not flash a skeleton for 80ms; do not show one indefinitely. After a stated timeout the surface becomes an honest error.
States
| State | Surface | Announcement |
|---|---|---|
| Loading | Skeleton matching the final layout | Polite "loading" only if it exceeds the threshold |
| First use | EmptyState with one action | Heading announced on focus |
| Filtered empty | Inline message plus a control to widen the range | Status message on filter change |
| Pending | CareCard with the expected timeframe | Status message on arrival |
| Error | Inline error with retry | Assertive, because the reader may act on absence |
| Withheld | Statement of what is off, plus a route to re-enable | Polite |
Content
Empty-state copy is where products are most tempted to be charming. In a health context, charm reads as evasion. The reader is looking at a screen that should have their data on it.
Do
"No blood-pressure readings yet. Log one and we'll show it against your range." One fact, one action, one reason.
Don’t
"Nothing to see here! 👀 Why not add some data?" Jokes about missing health data land badly with a reader who thought they had entered some.
Do
"We couldn't load your readings. This is a problem on our side. Try again."
Don’t
"No readings found." This is used for a failed request, which tells the reader their data is gone.
Error and empty message wording is owned by Error and empty messages; do not restate it here.
Accessibility
- The empty state has a heading at the same level the content would have had, so the document outline does not change shape depending on whether the reader has data.
- Skeletons are hidden from assistive technology (
aria-hidden), with a single polite status message doing the announcing. A dozen shimmering rectangles announced individually is worse than silence. - A state change from loading to loaded, or to error, is a status message (WCAG 2.2 SC 4.1.3). Errors that the reader might act on are announced assertively.
- The single action is the first focusable element after the heading.
- Illustrations are decorative and marked as such; an empty-state graphic must never be the only carrier of the message.
- Do not use motion to fill the space. An animated empty state is
attention-seeking with nothing to say, and it is the first thing removed under
prefers-reduced-motion. That means the layout has to work without it anyway.
Research
Updates to this page
Last read through against the system on 2026-09-20. Due for review every 12 months; expiry is reported by pnpm run check:freshness.
Consent and permissions
Asking for access at the moment it is needed, explaining what it buys, recording what was agreed, and making withdrawal as easy as agreement.
Offline and stale data
What a health screen shows when it cannot vouch for its numbers. It degrades assertions rather than hiding them, and never lets a cached value pass as current.