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.
A fact and a measurement are different values
“Ward 4B” and “1240 ng/L” are both values in a key–value pair, and only one of them has a unit, a reference interval, or any business being set in tabular numerals. This component treated them identically: every value went out monospaced and tabular, so a ward was typeset as though somebody had measured it, and a troponin whose unit had been left off rendered as a bare number.
A quantity carries its unit in the type. kind: "quantity"
requires unit; kind: "value" is for the things that have none. A
number without a unit is not a measurement, and there is now no way to render one as though it
were.
The comment above that prop used to read “the unit is never optional”, sitting on
a type that said unit?: string. The rule was right and nothing carried it.
What a value must not become
Every item below is offered by mainstream systems, and each one moves meaning out of the text and into something the reader has to already understand.
| Offered elsewhere | Verdict here |
|---|---|
| A hyphen where there is no data | No. It is the standard advice and it is half a fix: it stops a blank cell reading as a rendering fault, and it still says nothing about why. Not measured, awaiting result, not applicable, and the source being unreachable all arrive as the same character — see Three kinds of nothing. |
| An icon in place of the key, with a tooltip carrying the label | No, twice over. There is no icon set in this system, and a label revealed on hover does not exist on touch or to a keyboard — see Perceivable affordance and Tooltip. |
| A badge in place of the value, “because text is not emphatic enough” | No. The premise is worth taking seriously — a wall of grey text does flatten importance. The answer is not to make the value a badge: Badge is a count and nothing else, and a value that becomes a coloured pill has taken on a severity nobody assigned. Emphasis belongs in Status chip, which is a state and says so. |
| Right-aligned keys, or keys and values on the same line, chosen per pair | Only one arrangement per group. Mixing them inside one panel makes the eye re-find the key on every row, which is the whole cost the pair exists to avoid. |
Peak hs-cTnI
Awaiting result
drawn 15:10 · due ~15:40
The reader knows a sample exists, that nobody has failed to act, and roughly when to look again.
Peak hs-cTnI
—
Never ordered, still in the analyser, or the lab feed is down. The clinician cannot tell, and the most likely reading is that there is nothing to see.
What the component enforces, and what it only asks for
A key-value pair is presentational, which makes the split unusually clean: the type decides what can be said, and everything about whether it is true is yours.
| Rule | Held by | How |
|---|---|---|
| An absence states which kind of absence it is | The component | Maybe has four absent cases, so a missing value cannot be rendered as a blank or a dash.Asserted: KeyValue: not-measured renders as words, never a dash |
| A quantity always carries its unit | The component | quantity requires unit; a measured value without one is not expressible.Asserted: KeyValue: a quantity cannot be rendered without its unit |
| A value is not typeset as a measurement unless it is one | The component | value and quantity are different cases and render differently.Asserted: KeyValue: a fact is not typeset as a measurement |
| Provenance is shown where it exists | The component | provenance renders beneath the value.Asserted: KeyValue: provenance renders beneath the value |
| A pair is a term and its description | The component | A <dl> with <dt> and <dd>, one list per grid. The page said so while the component rendered spans in divs.Asserted: KeyValue: pairs are a description list, standalone or in a grid |
| The value is current | You | Nothing here fetches, polls or expires. A pair rendered once will sit unchanged for as long as you leave it on screen. |
| Provenance is accurate | You | It is a string. The component cannot check that the source named is the source the number came from. |
| A reference range applies to this patient | You | Ranges vary by age, sex, assay and laboratory. The component renders the one you pass. |
A rule stated on a component page reads as a rule the component keeps. 4 of these 7 are; 3 are obligations this page was quietly handing to the reader. The component can stop a blank standing where a result belongs. It cannot tell you that the result is still true.
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). 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.
- Unit always present and bound to the value. (the component) Mitigates: order-of-magnitude error from an assumed unit.
- Absence rendered explicitly, never blank. (the component) Mitigates: a missing result read as a normal one.
- Zero distinguished from not-measured. (the component) Mitigates: a measured absence and an unmeasured value being conflated.
- No substituted defaults. (the component) Mitigates: a previous or population value displayed as this patient's.
- Out-of-range stated, not alarmed. (you) Mitigates: alarm fatigue from range colouring, and dilution of the IEC palette.
- Key, value and unit never separated by wrap. (the component) Mitigates: a number read against the wrong label.
- Provenance on clinical values. (the component shows it; supplying it is you) Mitigates: a stale or wrong-encounter result read as current.
Implementation
$ npx shadcn@latest add https://md.notjustany.tech/r/key-value.json
<KeyValue
label= "Peak hs-cTnI"
value={{ kind: "quantity" , value: "1240" , unit: "ng/L" }}
reference= "< 34 ng/L"
provenance= "Central lab · 15:10"
/>
// A quantity requires its unit. A fact that has none - a ward, a sex -
// is kind: "value", and is not typeset as a measurement.
//
// Absence is a case of the value, not a missing one:
// { kind: "not-measured" } { kind: "not-applicable" }
// { kind: "pending" } { kind: "outside-scope", reason }
// so there is no way to render a blank where a result should be.
What the component takes. This table used to list previous, observedAt — props the component does not have, and never had. Found by the registry gate’s props-table rule.
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string |
Required. Full clinical name. | |
value | ClinicalValue |
Required. The union above; absence is a member, not a null. | |
provenance | string | — | Where the value came from — device, assay, model version, or the person who entered it. |
reference | string |
Range and measurement conditions where they exist. | |
children | React.ReactNode | — | On KeyValueGrid: the pairs, which it makes one description list. |
Laying several out together
KeyValueGrid ships with this install command and was described on no page. It
lays out a set of key–value pairs in columns that collapse to one on a narrow
screen.
- Reading order is the source of truth. The grid reflows from three columns to one, so the order in the markup is the order a screen-reader user and a 320 px reader both get. Anything whose meaning depends on sitting beside its neighbour is a Data grid, not this.
- Every pair keeps its own provenance. Laying values side by side does not let them share a source or a timestamp — a grid of results from three systems is three sources, stated three times.
- Absence still occupies its slot. A missing value does not close the gap up; it renders as the absence it is, so the shape of the record does not change with what happens to be known.
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.