Choose a component
Start from the data you have and end with the component that may honestly display it.
The task
You have a piece of health data, and it is a measurement, a derived index, a series, a state or a word. You have to decide what to render. The wrong choice here is not a styling problem. Putting a status colour on a category component implies a judgement nobody made; putting a score dial on a single measurement implies a composite that does not exist; putting an alert banner on information nobody has to act on spends the escalation budget on nothing and makes the next real alert invisible.
This recipe produces a decision, not a surface. Every other recipe assumes you have already made it.
What you need
Nothing to install for this recipe itself. It ends in a decision, not a surface. What you need is the shape of your datum and two facts about it: who assigned any clinical status it carries, and whether the reader needs the reference range to understand it. If you cannot answer the first question, you do not yet have a component decision to make. You have a clinical rules question, and the answer belongs in your product, not in the UI.
The components below are the full display roster, and every one of them is
shipped: the code exists and installs, and the API may change in any release.
Every one of them has been audited against WCAG 2.2 AA by its own authors; see
ADR 0025 for what that
audit checked and why an author-run audit is not an independent review. No
opsinjs component has had an independent accessibility review, and none has had
a clinical review. Nothing here is for a production health surface until a
clinician signs it. Read the fourth column before you install anything. The
wrong choice compiles exactly as well as the right one.
| What you have | Component | Status | Why not the obvious alternative |
|---|---|---|---|
| One measurement the reader is meant to interpret | ResultCard | Shipped | Not MetricTile: a tile is a glance, a result is a reading with meaning attached. |
| One measurement whose position in a range is the point | RangeBar | Shipped | Not ScoreDial: a dial implies a composite index, a range bar shows one measured value. |
| A composite index derived from several inputs | ScoreDial | Shipped | Not Value: a number whose derivation is hidden must show that it is derived. |
| The same measurement over time | TrendSparkline | Shipped | Not a chart library: the y-axis rules and the honest accessible description are the component's job. |
| One number at a glance, on a dashboard | MetricTile | Shipped | Not ResultCard: a tile that carries interpretation is a result card wearing the wrong size. |
| A clinical status on its own | StatusPill | Shipped | Not Callout: a callout is outside the status axis by design. |
| Something the reader has to read now | AlertBanner | Shipped | Not Dialog: interruption is the last rung, not the first. |
| Something the reader has to do, and by when | CareCard | Shipped | Not AlertBanner: a banner states, a care card instructs. |
| A word the reader may not know | Term | Shipped | Not a tooltip: the expansion must survive keyboard, touch and print. |
| A number that needs formatting | Value | Shipped | Not string interpolation: precision, unit adjacency and locale are correctness. |
| A timestamp | RelativeTime | Shipped | Not a formatted date: staleness thresholds are per metric and belong in the component. |
| Ordinary information, no clinical grading | Callout | Shipped | Not StatusPill: the status axis is reserved for clinical meaning. |
| The standard not-medical-advice note | DisclaimerNote | Shipped | Not free prose: placement and wording are governed, so the note is a component. |
| Nothing to show yet | EmptyState | Shipped | Not a zero: a zero is a measurement, an absence is not. |
| Data on its way | Skeleton | Shipped | Not a spinner over stale content: never let an old value read as a new one. |
| A bounded group of the above | Card · Surface | Shipped | Card is the content group; Surface is the material rung underneath it. |
Build it
Name the datum in the reader's words
Before anything else, write the label you would say out loud. "Resting heart
rate", not rhr. "How long you slept", not "sleep duration total minutes". If
the plain-English name needs a clinical term, that term is a
Term with an expansion, not a word you leave for the
reader to look up. The Plain-English A to Z
is the canonical vocabulary.
If you cannot name it in the reader's words, the problem is not the component.
Decide whether it carries a clinical status
A datum carries clinical status only if a rule your product owns assigned one: steady, watch, attention or urgent. The four levels and what each may mean are defined in Clinical status semantics.
Three things that are not a status: a category (heart, sleep, activity), a direction of change, and a position relative to a reference range. A value outside a reference range is outside a reference range. Whether that is watch or attention is a clinical judgement, and if nobody in your product has made it, the answer is that the datum has no status and no status colour appears.
Decide whether the reader needs the range
Most lab results and most single readings are meaningless without knowing what they are being compared to. For those, the range is part of the datum, not a detail, and the component has to carry it. That points at ResultCard or RangeBar, and at the Show a value against a reference range recipe.
If the reader already knows what good looks like for this number, the range is noise and a MetricTile is enough. Steps today and hours slept are examples.
Pick from the table
Match the shape, then read the fourth column. The fourth column is the useful one: it names the component you were about to reach for instead, and the reason that would have been wrong. Most incorrect choices are one of those pairs.
Check the two axes
Look at every coloured element in what you have chosen and say which axis it is on. Category colours identify. They say this is your heart data. Status colours grade. They say this needs attention. A single element carries one or the other, never both, and never colour alone. The two colour axes is the rule; a component that makes it hard to obey is a bug in the component.
Check the escalation budget
Count the surfaces on the finished screen that are at attention or urgent. The budget is one urgent surface per screen and it is not negotiable. See Alarm fatigue. If your choice puts you over budget, the fix is to demote, not to redesign the banner.
The copy
The decision you just made determines three strings, and they are the same three every time:
- The label says what was measured, in the reader's words. Never what it means: "Resting heart rate", not "Heart health".
- The unit sits adjacent to the number, never in a legend, never only in a heading. See Numbers, units & precision, which is canonical.
- The time says when it was measured, not when it was synced. A value with no time is a claim about the present that you cannot support.
Do
"Resting heart rate · 58 bpm · measured this morning". This gives what was measured, the unit beside the number, and when.
Don’t
"Heart Health: 58 Normal". Three faults in five words: an interpretation used as the label, a number with no unit, and a banned word.
"Normal" is banned system-wide. The reasoning is on Reference ranges: it means typical for a population to a clinician and fine, nothing to worry about to everybody else, and there is no context in a consumer app that disambiguates it.
Get it right
- No component computes a status. If your chosen component is deciding whether a value is watch or attention, the clinical rule has leaked into the UI. Move it out.
- Nothing is signalled by colour alone. Status is a word, an icon and a colour, in that order of importance. Verify by rendering the screen in greyscale: see Colour independence.
- Nothing is signalled by motion alone. Urgency never animates; see Motion in health UI.
- A composite score discloses its derivation. If you chose ScoreDial, the screen has to be able to answer "what is this made of?" without leaving it.
- Every choice has five data states. Empty, loading, error, stale and partial are defined once in Data states. Deciding the component is not finished until you have decided what it renders in all five.
- Touch targets clear 44pt with real separation between adjacent targets: Target size and motor accessibility.
Variations
The datum is a set of answers to questions. A symptom questionnaire, a mood scale, a medication schedule. These now have components: Questionnaire, SymptomPicker, ScaleInput, DoseTracker and GoalRing.
This recipe's decision table stops at display and formatting. The input-shaped components above exist and install, but the rule for choosing between a questionnaire, a scale and a dose tracker is not written here yet. Read each component's own page from Components until it is.
The datum came from a device. Then it carries provenance: which device, at what accuracy, and under what conditions. The display has to be able to show it. SourceCitation is the component for exactly this, and it ships.
The datum is a comparison against the reader's own history, not against a population range. That is a trend, not a result: go to Show a trend with an honest caption, and note that a personal baseline is a different claim from a reference range and must not be drawn as one.
The datum is an absence. No reading today is a legitimate thing to display and is not a zero. Use EmptyState with one clear next action; see Empty and first use.
Related
- Components is the full matrix, filterable by category.
- The two colour axes is the rule the fourth column of the table keeps referring to, in full.
- Clinical interaction guidelines covers ten prohibitions stated so you can test against them.