Components · Data

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.

Stable · v1.0 shadcn/ui WCAG 2.2 AA

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

The full form
Peak hs-cTnI
1 240ng/L
ref < 34 ng/L
drawn 15:10 · central lab
Baseline hs-cTnI
14ng/L
ref < 34 ng/L
drawn 14:41 · central lab
ST elevation
3.1mm
V2–V4, at J+60 ms
ECG 14:26:38 · model v4.2
PartRule
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

The distinction that matters most

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.

Absence, rendered explicitly
hs-cTnI
not measured
no sample requested
hs-cTnI
awaiting result
drawn 15:10 · due ~15:40
ST elevation
0.0mm
measured, no elevation
ECG 14:26:38
Prior ECG
none on file
change from baseline cannot be assessed

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.

Serial values with delta
hs-cTnI · 0/1 h
14 → 47ng/L
+33 ng/L over 60 min · ref < 34 ng/L
drawn 14:41 and 15:41 · central lab

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.

Out of range is not an alarm

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

Do
Peak hs-cTnI
1 240ng/L
ref < 34 ng/L
drawn 15:10

Label, value, unit, reference, provenance. Readable and checkable without leaving the card.

Don't
TnI
1240

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.

The rule

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 elsewhereVerdict 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.
Do

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.

Don't

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.

RuleHeld by How
An absence states which kind of absence it isThe 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 unitThe 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 oneThe 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 existsThe component provenance renders beneath the value.
Asserted: KeyValue: provenance renders beneath the value
A pair is a term and its descriptionThe 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 currentYou Nothing here fetches, polls or expires. A pair rendered once will sit unchanged for as long as you leave it on screen.
Provenance is accurateYou 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 patientYou Ranges vary by age, sex, assay and laboratory. The component renders the one you pass.
Why this is written down

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

Do
hs-cTnI
awaiting result
drawn 15:10 · due ~15:40
ST elevation
0.0mm
measured, no elevation

Awaiting and measured-zero rendered differently. Both say what happened.

Don't
hs-cTnI
—
ST elevation
—

Two dashes. One sample is in the lab, the other was measured as zero, and the column reads as unremarkable.

Do
Peak hs-cTnI
1 240ng/L
ref < 34 ng/L

Out of range stated in text, in the reference line. Survives greyscale and does not borrow an alarm hue.

Don't
Peak hs-cTnI
1 240ng/L

Abnormal values coloured red. On a sick patient the whole screen turns red and clinicians learn to discount it.

Accessibility

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.

Clinical safety notes

Risk controls this page specifies

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.

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.

PropTypeDefaultNotes
labelstring Required. Full clinical name.
valueClinicalValue Required. The union above; absence is a member, not a null.
provenancestring— Where the value came from — device, assay, model version, or the person who entered it.
referencestring Range and measurement conditions where they exist.
childrenReact.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.

NotJustAnyMed.Tech Design System · Key–value pair · v1.0 · draft for review
Reference applications named in this system are fictional; all patient data and reference ranges shown are fabricated and illustrative.