opsinjs
ComponentsHealth data display

ResultCard

One test result, shown in full. What was measured, the number, where it sits against its range, what that means in plain English, and what to do next.

Preview

Theme
Density
Text
Status
result-card · base base · style base-lyraOpen under the product theme

Installation

pnpm dlx shadcn@latest add @opsinjs/result-card

The @opsinjs namespace is declared in your components.json. Everything it installs is code you then own. There is no runtime package to keep in step.

Usage

import { ResultCard } from "@/components/ui/result-card"
import { EXAMPLE_SOURCE } from "@/lib/opsinjs"
<ResultCard
  title="Example measurement"
  value={14}
  unit="mg/dL"
  precision={0}
  measuredAt="2026-03-14T08:12:00+00:00"
  now="2026-03-14T11:12:00+00:00"
  range={{ low: 10, high: 20, source: EXAMPLE_SOURCE }}
  status="steady"
  category="labs"
  meaning="Two or three short sentences, in the second person, written by the product."
/>

When to use it

Use it when

  • A single result with room for its range, meaning and next step, behind authentication.
  • A result seen for the first time, where the explanation matters as much as the number.

Do not use it when

  • Several readings side by side in a dashboard grid.Use metric-tile instead.
  • The number has no range, no status and nothing to explain.Use value instead.
  • You only need to show where a value sits within its range.Use range-bar instead.
  • The main message is an instruction rather than a measurement.Use care-card instead.
  • Something has changed and must interrupt the reader now.Use alert-banner instead.

Clinical meaning

Asserts. A measurement was taken, at a time, in a unit. Where supplied, it also asserts a stated range and the level the product assigned. Every number goes through Value.

Never read as. A diagnosis, a summary of the reader's health, or a clinician's advice. A next step is the product's, and who is speaking lives in the words you write in meaning and in an action's label. A result that may distress needs Delivering difficult results around it.

Colour axis. Both, on different elements, as the two colour axes requires: status reaches only the StatusPill, category only the title, and no prop tints the surface.

Thresholds. The product's. No default range, no status derivation and no staleness boundary exist here. staleAfterHours passes through to RelativeTime. A reading you cannot vouch for must not drive attention or urgent.

Provenance and privacy. provenance is free text with no class, so keeping an estimated or self-reported value free of a status is your rule. There is no masking affordance; On-screen privacy is built around the card.

Anatomy

  1. ResultCardAn article named by its title. Neutral surface; layout through className.
  2. ResultCard.HeaderTitle, time and level. On a phone the level wraps beneath the time.
  3. ResultCard.TitleA real heading at titleLevel. The only element the category axis touches.Controlled by category
  4. ResultCard.TimeWraps RelativeTime with the absolute date. Carries the staleness words past a supplied boundary.Controlled by staleAfterHours
  5. StatusPillThe only status-coloured element. Rendered only when the product assigned a level.Controlled by status
  6. ResultCard.ReadingThe number at display size through Value, one per segment when compound.Controlled by value
  7. RangeBarDrawn only with a range, a unit and a single value. Given no status.Controlled by range
  8. ResultCard.MeaningThe plain-English paragraph. When absent, the sentence saying so.Controlled by meaning
  9. ResultCard.ActionsAt most two. An href renders a Link, onSelect a Button; the recommended one is bordered.Controlled by actions
  10. ResultCard.FootnoteProvenance only, when supplied. No default disclaimer.Controlled by provenance

Examples

A reading made of two numbers

segments carries a compound reading, each part a real Value. The unit prints once, is spoken after every part, and no bar is drawn.

Theme
Density
Text
Status
result-card-a-compound-reading · base base · style base-lyraOpen under the product theme

The smallest card, and the sentence it will not leave out

No range, so no bar; no status, so no pill; no provenance, so no footnote. An absent meaning still renders as a sentence saying there is none.

Theme
Density
Text
Status
result-card-no-explanation-supplied · base base · style base-lyraOpen under the product theme

The same result at two ages

Two cards differing only in measuredAt, with the same level, because age lowers what a reading is worth and does not raise the alarm.

Theme
Density
Text
Status
result-card-an-old-reading · base base · style base-lyraOpen under the product theme

Accessibility

Audited against WCAG 2.2 AA. A source pass and a rendered browser pass were both clean: zero axe violations, no target below the 44px floor, no focus-visible gap. It found nothing to fix, is author-run rather than an independent review, and leaves clinical review pending.

What it confirmed.

  • The two colour axes stay separate: category tint reaches only the title, clinical status only the StatusPill, which carries a word and icon.
  • Units speak through Value after each part of a compound reading, the title is a real heading at the caller's level, and the card reflows at 320px and 200% without clipping.

Documented, by design.

  • The root <article> takes aria-label from the title, not aria-labelledby. The string matches the visible heading and the region is non-interactive, so there is no name mismatch; a visible-heading reference is a future enhancement.
  • className merges onto the root, so a caller who passes truncate can clip a reading. The component truncates nothing; the JSDoc warns of it.

Not checked by any gate. Print, forced-colors and prefers-contrast.

KeyActionNotes
TabMoves to the first action, where the card has oneThe card itself is not a tab stop.
Shift+TabMoves backwards through the same stopsReverse order matches visual order.
EnterActivates the focused actionPlatform behaviour. Nothing is destructive.
SpaceActivates a focused button actionOn an onSelect action only.
EscapeNothingNot dismissible. Inside a Sheet or Dialog, Escape is theirs.
Arrow keysNothingThe bar is role="img", not a slider.
PairThemeAPCA LcWCAG 2.2Floor
activity ink on activity surfacelight88.59.61:1Pass
activity ink on activity surfacedark-83.112.58:1Pass
activity line on activity surfacelight67.64.42:1Pass
activity line on activity surfacedark-69.09.93:1Pass
activity accent on activity surfacelight44.12.34:1Below floor
activity accent on activity surfacedark-53.37.26:1Pass
activity accent on the pagelight46.12.41:1Below floor
activity accent on the pagedark-54.68.22:1Pass
activity ink on the pagelight90.59.90:1Pass
activity ink on the pagedark-84.414.24:1Pass
activity line on the bandlight66.14.32:1Pass
activity line on the banddark-67.48.60:1Pass
heart ink on heart surfacelight89.110.51:1Pass
heart ink on heart surfacedark-80.512.55:1Pass
heart line on heart surfacelight70.45.05:1Pass
heart line on heart surfacedark-64.59.46:1Pass
heart accent on heart surfacelight60.33.64:1Pass
heart accent on heart surfacedark-48.26.68:1Pass
heart accent on the pagelight63.23.81:1Pass
heart accent on the pagedark-49.57.27:1Pass
heart ink on the pagelight92.010.97:1Pass
heart ink on the pagedark-81.813.65:1Pass
heart line on the bandlight69.95.01:1Pass
heart line on the banddark-62.87.88:1Pass
labs ink on labs surfacelight88.89.78:1Pass
labs ink on labs surfacedark-83.112.69:1Pass
labs line on labs surfacelight67.94.51:1Pass
labs line on labs surfacedark-68.49.90:1Pass
labs accent on labs surfacelight59.33.49:1Pass
labs accent on labs surfacedark-52.77.21:1Pass
labs accent on the pagelight61.53.60:1Pass
labs accent on the pagedark-53.98.07:1Pass
labs ink on the pagelight90.910.09:1Pass
labs ink on the pagedark-84.314.22:1Pass
labs line on the bandlight66.54.42:1Pass
labs line on the banddark-66.78.49:1Pass
mind ink on mind surfacelight89.910.36:1Pass
mind ink on mind surfacedark-81.312.56:1Pass
mind line on mind surfacelight70.74.94:1Pass
mind line on mind surfacedark-65.49.54:1Pass
mind accent on mind surfacelight60.03.55:1Pass
mind accent on mind surfacedark-49.16.77:1Pass
mind accent on the pagelight62.83.70:1Pass
mind accent on the pagedark-50.07.42:1Pass
mind ink on the pagelight92.710.80:1Pass
mind ink on the pagedark-82.213.77:1Pass
mind line on the bandlight70.04.89:1Pass
mind line on the banddark-63.48.00:1Pass
nutrition ink on nutrition surfacelight89.310.06:1Pass
nutrition ink on nutrition surfacedark-81.912.55:1Pass
nutrition line on nutrition surfacelight69.74.78:1Pass
nutrition line on nutrition surfacedark-66.79.66:1Pass
nutrition accent on nutrition surfacelight39.22.11:1Below floor
nutrition accent on nutrition surfacedark-50.26.88:1Pass
nutrition accent on the pagelight41.82.19:1Below floor
nutrition accent on the pagedark-51.37.63:1Pass
nutrition ink on the pagelight91.910.44:1Pass
nutrition ink on the pagedark-82.913.91:1Pass
nutrition line on the bandlight68.84.71:1Pass
nutrition line on the banddark-64.88.19:1Pass
sleep ink on sleep surfacelight89.610.19:1Pass
sleep ink on sleep surfacedark-81.912.60:1Pass
sleep line on sleep surfacelight70.04.80:1Pass
sleep line on sleep surfacedark-66.49.65:1Pass
sleep accent on sleep surfacelight64.64.05:1Pass
sleep accent on sleep surfacedark-50.46.94:1Pass
sleep accent on the pagelight67.34.22:1Pass
sleep accent on the pagedark-51.47.67:1Pass
sleep ink on the pagelight92.310.60:1Pass
sleep ink on the pagedark-82.913.92:1Pass
sleep line on the bandlight69.14.75:1Pass
sleep line on the banddark-64.58.16:1Pass
body text on the pagelight101.617.18:1Pass
body text on the pagedark-100.518.00:1Pass
body text on a cardlight104.717.96:1Pass
body text on a carddark-99.616.32:1Pass
body text on the muted groundlight98.116.32:1Pass
body text on the muted grounddark-97.513.77:1Pass
secondary text on the pagelight83.97.42:1Pass
secondary text on the pagedark-80.713.44:1Pass
secondary text on a cardlight87.07.76:1Pass
secondary text on a carddark-79.812.19:1Pass
secondary text on the muted groundlight80.47.05:1Pass
secondary text on the muted grounddark-77.810.28:1Pass
a hairline boundary on the pagelight19.31.41:1Below floor
a hairline boundary on the pagedark-8.21.90:1Below floor
a hairline boundary on a cardlight22.41.47:1Below floor
a hairline boundary on a carddark-7.31.72:1Below floor
a hairline boundary on the muted groundlight15.81.34:1Below floor
a hairline boundary on the muted grounddark0.01.45:1Below floor
a placeholder boundary on the pagelight69.84.52:1Pass
a placeholder boundary on the pagedark-51.07.62:1Pass
a placeholder boundary on a cardlight72.84.72:1Pass
a placeholder boundary on a carddark-50.16.91:1Pass
a placeholder boundary on the muted groundlight66.24.29:1Pass
a placeholder boundary on the muted grounddark-48.15.83:1Pass
a placeholder fill on the pagelight8.31.18:1Below floor
a placeholder fill on the pagedark-8.21.90:1Below floor
a placeholder fill on a cardlight11.41.23:1Below floor
a placeholder fill on a carddark-7.31.72:1Below floor
a placeholder fill on the muted groundlight0.01.12:1Below floor
a placeholder fill on the muted grounddark0.01.45:1Below floor
a placeholder boundary on its own filllight58.93.84:1Pass
a placeholder boundary on its own filldark-41.54.01:1Below floor
the card hairline on the pagelight69.84.52:1Pass
the card hairline on the pagedark-28.74.19:1Below floor
the card hairline on a cardlight72.84.72:1Pass
the card hairline on a carddark-27.93.80:1Below floor
activity ink on the card materiallight93.610.34:1Pass
activity ink on the card materialdark-83.512.91:1Pass
heart ink on the card materiallight95.111.47:1Pass
heart ink on the card materialdark-80.912.38:1Pass
labs ink on the card materiallight94.010.54:1Pass
labs ink on the card materialdark-83.512.90:1Pass
mind ink on the card materiallight95.711.28:1Pass
mind ink on the card materialdark-81.412.49:1Pass
nutrition ink on the card materiallight95.010.91:1Pass
nutrition ink on the card materialdark-82.012.61:1Pass
sleep ink on the card materiallight95.411.08:1Pass
sleep ink on the card materialdark-82.112.62:1Pass

These are the measured token pairs this component draws colour from, not a measurement of the component itself.

Data attributes

AttributeConditionValue
data-slotOn every part, alwaysresult-card, result-card-header, -title, -time, -reading, -meaning, -actions, -footnote; result-card-action on a link action, button on an onSelect action
data-statusOn the embedded StatusPill only, with a statussteady | watch | attention | urgent
data-categoryOn ResultCard.Title, for one of the six categoriessleep | heart | activity | nutrition | mind | labs
data-opsinjs-valueOn every Value, including a drawn RangeBar's. De-duplicate before scraping.The unrounded number, or an empty string with no reading

API reference

Prop

Type

Generated from ResultCardProps in registry/bases/base/result-card.tsx.

now and precision are required: the card never reads the clock, so pass one now to every card on a screen. value is number | null, never a string; a pair is segments. A third action, a second recommended and a titleLevel outside 2 to 6 each warn once and fall back.

  • MetricTile is the same data with no room to explain itself.
  • Card is a neutral box with no clinical contract.
  • CareCard is a card whose subject is an instruction, not a measurement.
  • AlertBanner is for something that must interrupt.
  • DisclaimerNote is where the not-medical-advice note lives.

On this page