Key–value pair
The smallest unit of clinical display: a label, a number, a unit. Almost every rule in this system converges on getting these three inseparable — and on the three different things "no value" can mean.
Overview
A key–value pair looks too small to have rules. It is where the most consequential display errors happen: a value separated from its unit, a label paired with the wrong number, a blank cell read as a normal result.
Everything on this page follows from one observation — the number alone is never the
information. 47 is meaningless; 47 ng/L, reference < 34, drawn
14:41 is a clinical fact.
Inseparable
Label, value and unit are one object. Nothing wraps, truncates or reflows between them, and nothing hides one behind a hover.
Comparable
A value means little without its reference range and its predecessor. Where they exist, they are shown.
Explicit about absence
Not measured, not yet resulted, and genuinely zero are three different states and never share a rendering.
Anatomy
| Part | Rule |
|---|---|
| Key | The full clinical name, never an internal code. Permanently visible above or beside the value — never a placeholder, never a tooltip. |
| Value | Mono, tabular numerals, thin-space thousands separator. Bound to its unit with a non-breaking space. |
| Unit | Always present. Visually lighter than the number but never hidden, never inferred, never only in the column header. |
| Reference | Where one exists. Also carries the measurement conditions where they matter — the lead, the timing point. |
| Provenance | When and from where. The same requirement as Card, at a finer grain. |
Three kinds of nothing
A blank cell is the most dangerous rendering in clinical software, because a clinician scanning a column reads absence as unremarkable. Not measured, awaiting result and measured as zero are clinically different and are never rendered the same way. None of them is ever a blank.
- Never a blank, never a bare dash without an adjacent explanation.
—alone tells a clinician nothing about why. - Zero is a measurement.
0.0 mmrenders in the normal value style, because someone looked and found none. - Awaiting carries an expectation — when it was requested and roughly when it is due, so a clinician can decide whether to wait.
- Never substitute a default. A missing value is never displayed as zero, as the previous value, or as a population norm.
Change and reference
A single value is rarely the clinical question; the trajectory usually is. Where a prior exists, the pair shows the change and the interval that produced it.
Both values, the delta, the interval and the reference. The clinician is not asked to do arithmetic, and can check the arithmetic that was done.
A value outside its reference range is stated, not alarmed. It gets no IEC hue, no red text and no icon — those belong to Alert Banner, which fires on a clinical rule rather than on a range boundary. Colouring every abnormal value red produces a screen of red on a sick patient and teaches clinicians to discount it. Where a threshold genuinely warrants action, the alarm says so and the value stays a value.
Layout
- Stacked by default — key above value. Survives narrow widths and long translated labels without any reflow logic.
- Aligned two-column only in dense panels, where the keys are short and the values benefit from sharing a decimal alignment.
- Never a wrapping row of pairs. A row that reflows can put a key on one line and its value on the next, which is the failure this component exists to prevent.
- Group gaps larger than internal gaps — see Spacing. Inside a pair, 4 px; between pairs, at least 16 px.
Label, value, unit, reference, provenance. Readable and checkable without leaving the card.
Abbreviated key, no unit, no reference, no provenance, and coloured with an alarm hue for being abnormal. Four separate rules broken in one line.
Do's and don'ts
Awaiting and measured-zero rendered differently. Both say what happened.
Two dashes. One sample is in the lab, the other was measured as zero, and the column reads as unremarkable.
Out of range stated in text, in the reference line. Survives greyscale and does not borrow an alarm hue.
Abnormal values coloured red. On a sick patient the whole screen turns red and clinicians learn to discount it.
Accessibility
- A real description list —
<dl>with<dt>and<dd>— so the pairing is programmatic, not just visual. - The unit is inside the value, so a screen reader announces "1 240 nanograms per litre" as one thing rather than two adjacent fragments.
- Abbreviated units are expanded for assistive technology where the abbreviation is not pronounceable.
- Absence is text, not styling. "not measured" is read out; a greyed blank is not.
- Never colour alone for out-of-range — the reference line carries it in text, which also survives greyscale printing.
- Thin-space separators use
, which does not break and is not announced as a word.
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 — the value read is the value meant, in the units meant, for the label meant. Absence is understood as absence rather than as normality.
- Efficiency — no arithmetic and no cross-referencing to interpret a result, which is where most of the reading time in a record goes.
- Satisfaction — confidence in a number without opening its source. Values that must be verified elsewhere are values the display failed to present.
Clinical safety notes
Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1).
- Unit always present and bound to the value. Mitigates: order-of-magnitude error from an assumed unit.
- Absence rendered explicitly, never blank. Mitigates: a missing result read as a normal one.
- Zero distinguished from not-measured. Mitigates: a measured absence and an unmeasured value being conflated.
- No substituted defaults. Mitigates: a previous or population value displayed as this patient's.
- Out-of-range stated, not alarmed. Mitigates: alarm fatigue from range colouring, and dilution of the IEC palette.
- Key, value and unit never separated by wrap. Mitigates: a number read against the wrong label.
- Provenance on clinical values. Mitigates: a stale or wrong-encounter result read as current.
Implementation
$ npx shadcn@latest add https://md.notjustany.tech/r/key-value.json
// Absence is a variant of the value, not the absence of one.
type ClinicalValue =
| { kind: "measured"; value: number; unit: string }
| { kind: "pending"; requestedAt: string; dueAt?: string }
| { kind: "notTaken"; reason?: string }
| { kind: "unavailable"; reason: string }
<KeyValue
label="Peak hs-cTnI"
value={{ kind: "measured", value: 1240, unit: "ng/L" }}
reference="< 34 ng/L"
observedAt="2026-08-13T15:10:00Z"
source="Central lab"
/>
// There is no way to render a value without a unit, and no way to
// render nothing at all.
if (value.kind === "measured" && !value.unit) {
throw new Error("[KeyValue] a measured value requires a unit.")
}
| Prop | Type | Notes |
|---|---|---|
label | string |
Required. Full clinical name. |
value | ClinicalValue |
Required. The union above; absence is a member, not a null. |
reference | string |
Range and measurement conditions where they exist. |
previous | ClinicalValue |
Renders the delta and interval automatically. |
observedAt · source | string |
Required for clinical values. |
Related
- Card — the usual container, and the same provenance rule.
- Typography — numerals, units, separators.
- Spacing — why the internal gap is smaller.
- Alert Banner — where a threshold crossing belongs instead.