opsinjs
ComponentsHealth communication

SourceCitation

Where a piece of health information came from, in plain words, with an optional link to the fuller citation and an optional date it was last checked.

Preview

Theme
Density
Text
Status
source-citation · base base · style base-lyraOpen under the product theme

Installation

pnpm dlx shadcn@latest add @opsinjs/source-citation

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 { SourceCitation } from "@/components/ui/source-citation"
<SourceCitation
  source="Estimated by your watch from movement and heart rate."
  more={{ label: "Read how this is worked out", href: "/how-it-works" }}
  checkedOn="2026-03-14"
  locale="en-GB"
/>

When to use it

Use it when

  • Beside a health value whose provenance a reader needs to weigh it: a wearable estimate, a home device reading, a typed figure.
  • To attribute and link a manufacturer's accuracy document or a study you have read, rather than restating its claim as your own.
  • Wherever a number's origin bounds what may be said about it, so origin travels with the number where it is read.

Do not use it when

  • The message is a standing statement about the whole product, rather than the source of one value.Use disclaimer-note instead.
  • You are saying when a reading was taken or synced, rather than where it came from.Use relative-time instead.
  • You are explaining what a term means, rather than where a value came from.Use term instead.
  • The note is information about the content with no source to attribute.Use callout instead.

Clinical meaning

Asserts. Where a health value came from, in the product's own plain words, with an optional checked date and a link to the fuller source.

Never read as. A guarantee that the source is right, current, or checked by anybody. checkedOn is the date the product recorded. A value old enough to doubt is wrapped in RelativeTime beside it.

Colour axis. Neither: no status prop and no category prop, and a class from either axis raises a development warning. See The two colour axes.

Thresholds. None. No reference range, no threshold, no verdict, and no clinical number passes through.

Who owns the words. The product. Data provenance and device accuracy sets ten rules, and this component carries two. Rule 2 puts provenance in plain words at the value. Rule 6 attributes a manufacturer's claim rather than restating it. The other eight stay the caller's, and it renders a wrong string as readily as a right one. opsinjs ships no source, no date, no DOI and no study. See Evidence and references.

Anatomy

  1. SourceCitationThe root. Quiet and outside both colour axes: no fill, no radius, no role.Controlled by className
  2. SourceCitation.SourceThe product's plain words for where the value came from. Given nothing, it prints one line saying so.Controlled by source
  3. SourceCitation.CheckedThe checked date, one line down in quieter ink. The component prefixes it; a time element carries the machine date.Controlled by checkedOn
  4. SourceCitation.LinkThe fuller citation. Label and destination arrive together, on its own line, keeping the full target size.Controlled by more

Examples

A device estimate naming its own method

The provenance names the device and the class of the figure, so it reads as an estimate. more points at the fuller accuracy document.

Theme
Density
Text
Status
source-citation-a-device-estimate · base base · style base-lyraOpen under the product theme

When nobody said where it came from

With no source, the citation prints an admission rather than rendering empty. Markup you wrap a source in is taken at its word.

Theme
Density
Text
Status
source-citation-no-source-supplied · base base · style base-lyraOpen under the product theme

Content guidelines

Name the concrete thing rather than the brand's marketing, and say the class of the figure: an estimate is called an estimate. Name the link for where it goes, since a screen reader may list it alone. A vague label raises a development warning in English only.

Do

"Estimated by your watch from movement and heart rate." Names the device, the inputs and the class of the figure.

Don’t

"Powered by clinically validated technology." Names no device and no method.

Accessibility

This component was audited against WCAG 2.2 AA in two passes, one over the source and one over the rendered output. The audit is author-run, not independent, and clinical review is still pending. Both passes were clean: no axe violations, target sizes met, focus visible, and no loss at 320px reflow.

  • No px type size. Both lines are set at text-opsin-footnote, a rem from the generated ramp.
  • No raw colour literal and no ramp step. The source ink is --foreground and the date ink is --muted-foreground.
  • more carries the label and the href together, so a link cannot be added without a label. An external href adds an off-screen "opens in a new tab".
  • The link is not inside a sentence, so it re-adds --opsin-target-minimum on both axes. See Target size and motor.
  • It reflows at 200% text: no fixed height, min-w-0 with wrap-break-word, so a long device name breaks.

Residual, left for you to weigh.

  • The source line, the last-checked time and the fuller-citation link render as loose siblings with no programmatic group binding them into one citation. Reading order keeps the association, so this is not an AA failure.
  • The "Last checked on" chrome is always English while the date follows the caller's locale; when they differ, the localized date carries no lang seam yet, per ADR 0005.
KeyActionNotes
TabMoves to the link, when there is oneThe citation is text, not a tab stop. The link is its only focusable element.
Shift+TabMoves back out of the linkReverse order matches visual order. The link is last.
EnterFollows the linkOnly when a link is present. An ordinary anchor, so Enter and not Space.
SpaceScrolls the page, as it does anywhere elseNothing here captures Space or activates on it.
PairThemeAPCA LcWCAG 2.2Floor
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

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

API reference

Prop

Type

Generated from SourceCitationProps in registry/bases/base/source-citation.tsx.

source is typed optional but required by the contract, so the missing state can render. checkedOn is a strict YYYY-MM-DD string or epoch milliseconds, both pinned to UTC. A value that is not a real calendar date is refused, not rendered as a plausible wrong one.

  • DisclaimerNote: the standing statement about the whole product, not one value.
  • RelativeTime: when a reading was taken or synced, not where it came from.
  • Callout: information about the content with no source to attribute.
  • Term: what a word means, rather than where a value came from.

On this page