opsinjs
ComponentsHealth data display

RangeBar

A single measurement drawn on a line, so you can see at a glance where your number sits compared with the range it is usually expected to fall in.

Preview

Theme
Density
Text
Status
range-bar · base base · style base-lyraOpen under the product theme

Installation

pnpm dlx shadcn@latest add @opsinjs/range-bar

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 { RangeBar } from "@/components/ui/range-bar"
<RangeBar
  label="Example measurement"
  value={14}
  unit="mg/dL"
  precision={0}
  locale="en-GB"
  range={{ low: 10, high: 20, source: "Example data rather than a reference range" }}
/>

When to use it

Use it when

  • One measurement with a published reference range, shown to its owner.
  • A value whose meaning is positional rather than absolute.
  • A reader who would otherwise compare two numbers in their head.

Do not use it when

  • There is no range, or it turns on facts you lack: age, sex, medication, assay.Use value instead.
  • You are showing change over time, not position.Use trend-sparkline instead.
  • The number is a score you calculated, not a measurement.Use score-dial instead.
  • Only the clinical status level matters.Use status-pill instead.
  • You want to say what to do about it.Use care-card instead.

Clinical meaning

Asserts. This measurement, in this unit, at this position against this range, from this source. Nothing about cause or trend.

Never read as. A diagnosis or verdict. Outside the range is not "abnormal", inside is not "normal": Reference ranges has the banned words.

Colour axis. Status only, when the product supplies one. category tints the label alone; position never becomes a status. The two colour axes.

Thresholds. The product owns every range, status and precision. No built-in table, no staleAfterHours. Staleness comes from RelativeTime.

Anatomy

  1. RangeBarThe root. Colour from neither axis.
  2. RangeBar.LabelWhat was measured, the only category-tinted part.Controlled by category
  3. StatusPillThe level the product assigned.Controlled by status
  4. RangeBar.TrackThe drawn extent, aria-hidden, never a slider.Controlled by range
  5. RangeBar.BandThe range itself, outlined, never axis-coloured.
  6. RangeBar.MarkA boundary and its number, at the metric's precision.Controlled by precision
  7. RangeBar.TickThe reading's position, the only status-coloured part.Controlled by value
  8. RangeBar.ValueThe reading and its unit, for the eye alone.Controlled by unit
  9. RangeBar.SummaryReading, position, range and source. Rewordable, never removable.Controlled by summary
  10. RangeBar.FootnoteWhen the reading was taken, or that nobody knows.Controlled by measuredAt
  • RangeBar 1
    • RangeBar.Label 1
    • StatusPill 0..1
    • RangeBar.Track 0..1
      • RangeBar.Band 1
      • RangeBar.Mark 2
      • RangeBar.Tick 1
        • RangeBar.Value 1
    • RangeBar.Summary 1
    • RangeBar.Footnote 0..1

Examples

Beyond the top of the range

The tick sits outside the band, and that is not a verdict.

Theme
Density
Text
Status
range-bar-outside-the-range · base base · style base-lyraOpen under the product theme

One bound only

One bound has no width, so no track is drawn. The summary names the direction.

Theme
Density
Text
Status
range-bar-an-open-ended-range · base base · style base-lyraOpen under the product theme

Three readings down a page

category on the labels, status on the ticks, no element taking both.

Theme
Density
Text
Status
range-bar-in-a-list-of-results · base base · style base-lyraOpen under the product theme

Far above the range

The extent stretches to contain value, so a far reading squeezes the band.

Theme
Density
Text
Status
range-bar-far-above-the-range · base base · style base-lyraOpen under the product theme

States

Data stateWhat RangeBar renders
ErrorIt owns no fetch. A range with no source, neither bound, or downward bounds is discarded and reported.
LoadingNo skeleton or spinner; renders synchronously.
Emptyvalue of null differs from 0: no tick, and the summary names the range.
PartialNo range, or one bound, draws no track. The summary carries the comparison.
StaleIt states when the reading was taken, or that nobody knows. No boundary.

Content guidelines

The label is what was measured, in the reader's words. The summary states position, never a verdict.

Do

"26 mg/dL, above the range 10 to 20 mg/dL, from Example data."

Don’t

"26 mg/dL. Abnormal. Your result is poor." A verdict with no basis.

Accessibility

Audited against WCAG 2.2 AA over the source and the rendered preview. The audit is author-run, not independent, and clinical review is pending. The rendered pass was clean: no axe violations, no focus-visible failure, no horizontal scroll at 320px. It is display-only, so nothing is focusable and the keyboard and target-size checks do not apply.

  • The summary paragraph is unconditional: reading, range with its source, and within, above or below in words, so the picture is never named.
  • RangeBar.Track is aria-hidden, with no slider, meter or progressbar role.
  • Numbers go through Value, so the unit is spoken, not spelled.
  • Status carries a word and a glyph through StatusPill, redundant to the tick colour.
  • Under forced-colors the tick keeps a Highlight fill and CanvasText outline.

Residual. The band outline and the two boundary marks fall below the 3:1 non-text contrast floor. They stay because the graphic is aria-hidden and the summary carries the same facts. Its own text and colour pairs are not measured yet, so the report below stands in.

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
attention ink on attention surfacelight89.410.35:1Pass
attention ink on attention surfacedark-81.112.54:1Pass
attention line on attention surfacelight70.04.90:1Pass
attention line on attention surfacedark-65.59.54:1Pass
attention accent on attention surfacelight65.14.18:1Pass
attention accent on attention surfacedark-49.46.80:1Pass
attention ink on the attention bandlight22.42.48:1Below floor
attention ink on the attention banddark-31.01.84:1Below floor
attention accent on the pagelight67.84.35:1Pass
attention accent on the pagedark-50.57.47:1Pass
attention ink on the pagelight92.210.77:1Pass
attention ink on the pagedark-82.313.76:1Pass
steady ink on steady surfacelight88.39.62:1Pass
steady ink on steady surfacedark-82.912.56:1Pass
steady line on steady surfacelight67.54.45:1Pass
steady line on steady surfacedark-68.99.91:1Pass
steady accent on steady surfacelight56.43.21:1Pass
steady accent on steady surfacedark-53.27.25:1Pass
steady ink on the steady bandlight30.03.00:1Below floor
steady ink on the steady banddark-28.81.73:1Below floor
steady accent on the pagelight58.63.31:1Pass
steady accent on the pagedark-54.58.19:1Pass
steady ink on the pagelight90.69.94:1Pass
steady ink on the pagedark-84.214.20:1Pass
unknown ink on unknown surfacelight89.59.98:1Pass
unknown ink on unknown surfacedark-82.212.56:1Pass
unknown line on unknown surfacelight69.24.64:1Pass
unknown line on unknown surfacedark-67.29.72:1Pass
unknown accent on unknown surfacelight43.12.29:1Below floor
unknown accent on unknown surfacedark-51.37.05:1Pass
unknown accent on the pagelight45.62.37:1Below floor
unknown accent on the pagedark-52.37.84:1Pass
unknown ink on the pagelight92.010.34:1Pass
unknown ink on the pagedark-83.113.97:1Pass
urgent ink on urgent surfacelight88.210.58:1Pass
urgent ink on urgent surfacedark-80.312.52:1Pass
urgent line on urgent surfacelight70.05.09:1Pass
urgent line on urgent surfacedark-64.89.52:1Pass
urgent accent on urgent surfacelight72.35.58:1Pass
urgent accent on urgent surfacedark-48.06.63:1Pass
urgent ink on the urgent bandlight13.91.90:1Below floor
urgent ink on the urgent banddark-31.51.89:1Below floor
urgent accent on the pagelight75.35.84:1Pass
urgent accent on the pagedark-49.57.24:1Pass
urgent ink on the pagelight91.211.06:1Pass
urgent ink on the pagedark-81.813.66:1Pass
watch ink on watch surfacelight89.410.02:1Pass
watch ink on watch surfacedark-82.112.58:1Pass
watch line on watch surfacelight69.54.72:1Pass
watch line on watch surfacedark-66.89.68:1Pass
watch accent on watch surfacelight61.73.72:1Pass
watch accent on watch surfacedark-50.76.95:1Pass
watch ink on the watch bandlight25.72.70:1Below floor
watch ink on the watch banddark-30.61.81:1Below floor
watch accent on the pagelight64.23.85:1Pass
watch accent on the pagedark-51.77.71:1Pass
watch ink on the pagelight91.910.38:1Pass
watch ink on the pagedark-83.113.95: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
attention surface on the card materiallight0.01.09:1Below floor
attention surface on the card materialdark0.01.00:1Below floor
steady surface on the card materiallight0.01.08:1Below floor
steady surface on the card materialdark0.01.03:1Below floor
urgent surface on the card materiallight0.01.09:1Below floor
urgent surface on the card materialdark0.01.01:1Below floor
watch surface on the card materiallight0.01.08:1Below floor
watch surface on the card materialdark0.01.01:1Below floor
the focus ring on the attention surfacelight67.34.44:1Pass
the focus ring on the attention surfacedark-45.96.30:1Pass
attention ink on the neutral hover filllight88.610.23:1Pass
attention ink on the neutral hover filldark-79.310.53:1Pass
the focus ring on the steady surfacelight67.84.47:1Pass
the focus ring on the steady surfacedark-45.86.12:1Pass
steady ink on the neutral hover filllight87.09.43:1Pass
steady ink on the neutral hover filldark-81.310.86:1Pass
the focus ring on the urgent surfacelight67.04.42:1Pass
the focus ring on the urgent surfacedark-45.56.34:1Pass
urgent ink on the neutral hover filllight87.710.50:1Pass
urgent ink on the neutral hover filldark-78.910.46:1Pass
the focus ring on the watch surfacelight67.64.46:1Pass
the focus ring on the watch surfacedark-46.06.24:1Pass
watch ink on the neutral hover filllight88.39.86:1Pass
watch ink on the neutral hover filldark-80.110.67: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, alwaysrange-bar, range-bar-label, status-pill, range-bar-track, range-bar-band, range-bar-mark, range-bar-tick, range-bar-value, range-bar-summary, range-bar-footnote
data-statusOn the StatusPill and the tick, when a status was suppliedsteady | watch | attention | urgent
data-categoryOn the label, when a category was suppliedsleep | heart | activity | nutrition | mind | labs
data-opsinjs-valueOn every Value, six per drawn barThe unrounded number, or empty with no reading

CSS variables

SelectorVariableControls
div:has(> [data-slot="range-bar-track"])--rb-label-halfHow far the reading label's midpoint is clamped from either track edge. Set inline at 3.5em.

API reference

Prop

Type

Generated from RangeBarProps in registry/bases/base/range-bar.tsx.

precision is decimal places, not significant figures, and it is required. A status outside the four levels is dropped, never approximated. Omit locale and the server formats with its own while the browser uses the reader's, so the two disagree.

On this page