Reference Range

A horizontal segmented bar that places a value against an ordered set of zones. Designed for clinical reference ranges (LDL, hsCRP, BUN), but works equally well for credit scores, AQI, battery health, or any severity-coded reading.

Open in

Preview

130mg/dL
2080110200
import { ReferenceRange } from "@/components/ui/reference-range"

export default function BasicExample() {
  return (
    <div className="w-full max-w-md">
      <ReferenceRange
        value={130}
        unit="mg/dL"
        ranges={[
          { start: 0, end: 20, color: "var(--range-1)" },
          { start: 20, end: 80, color: "var(--range-4)" },
          { start: 80, end: 110, color: "var(--range-5)" },
          { start: 110, end: 200, color: "var(--range-3)" },
          { start: 200, end: 250, color: "var(--range-2)" },
        ]}
      />
    </div>
  )
}

Installation

pnpm dlx shadcn@latest add https://ui-registry-delta.vercel.app/r/reference-range.json

Color tokens

color is passed straight to backgroundColor — any valid CSS color works (hex, oklch, hsl, or a var() reference). The starter kit below defines five severity tokens you can paste into globals.css.

/* Add to your globals.css. Severity scale: 1 = very bad, 5 = excellent.
   Delete, rename, or add more (--range-6, --range-7) as you need. */
@theme inline {
  --color-range-1: var(--range-1);
  --color-range-2: var(--range-2);
  --color-range-3: var(--range-3);
  --color-range-4: var(--range-4);
  --color-range-5: var(--range-5);
}

:root {
  --range-1: oklch(0.62 0.20 25);
  --range-2: oklch(0.72 0.16 32);
  --range-3: oklch(0.82 0.14 80);
  --range-4: oklch(0.80 0.15 145);
  --range-5: oklch(0.62 0.17 150);
}

.dark {
  --range-1: oklch(0.60 0.19 25);
  --range-2: oklch(0.70 0.15 32);
  --range-3: oklch(0.80 0.14 80);
  --range-4: oklch(0.78 0.16 145);
  --range-5: oklch(0.62 0.18 150);
}

Usage

import { ReferenceRange } from "@/components/ui/reference-range"
<ReferenceRange
  value={135}
  unit="mg/dL"
  ranges={[
    { start: null, end: 100, color: "var(--range-5)" },
    {              end: 130, color: "var(--range-4)" },
    {              end: 160, color: "var(--range-3)" },
    {              end: 190, color: "var(--range-2)" },
    {              end: null, color: "var(--range-1)" },
  ]}
/>

Boundaries are [start, end) — start inclusive, end exclusive. The last range is inclusive on both sides. Only the first range takes start; subsequent ranges derive it from the previous end, so just specify end.

Examples

Open-ended bookends

Pass start: null or end: null for < X / > Y bookends. Open segments size to the average closed-segment width, and stretch if the value overflows the declared domain.

LDL Cholesterol

185mg/dL
100115160190
import { ReferenceRange } from "@/components/ui/reference-range"

export default function OpenEndedExample() {
  return (
    <div className="w-full max-w-md">
      <p className="mb-3 text-sm">LDL Cholesterol</p>
      <ReferenceRange
        value={185}
        unit="mg/dL"
        ranges={[
          { start: null, end: 100, color: "var(--range-5)", label: "Optimal" },
          { start: 100, end: 115, color: "var(--range-4)", label: "Near optimal" },
          { start: 115, end: 160, color: "var(--range-3)", label: "Borderline" },
          { start: 160, end: 190, color: "var(--range-2)", label: "High" },
          { start: 190, end: null, color: "var(--range-1)", label: "Very high" },
        ]}
      />
    </div>
  )
}

Equal distribution

Use distribution="equal" when narrow zones get squeezed too thin. Ticks stay numerically positioned.

hsCRP — equal segments

2.4mg/L
13
import { ReferenceRange } from "@/components/ui/reference-range"

export default function EqualDistributionExample() {
  return (
    <div className="w-full max-w-md">
      <p className="mb-3 text-sm">hsCRP — equal segments</p>
      <ReferenceRange
        value={2.4}
        unit="mg/L"
        distribution="equal"
        ranges={[
          { start: 0, end: 1, color: "var(--range-5)" },
          { start: 1, end: 3, color: "var(--range-3)" },
          { start: 3, end: 10, color: "var(--range-1)" },
        ]}
      />
    </div>
  )
}

Labeled zones with custom ticks

Add a label per range to show on hover, and pass tickLabels as an explicit array to control which boundaries are annotated.

Battery health

78%
507085
import { ReferenceRange } from "@/components/ui/reference-range"

export default function LabeledZonesExample() {
  return (
    <div className="w-full max-w-md">
      <p className="mb-3 text-sm">Battery health</p>
      <ReferenceRange
        value={78}
        unit="%"
        tickLabels={[50, 70, 85]}
        ranges={[
          { start: 0, end: 50, color: "var(--range-1)", label: "Failing" },
          { start: 50, end: 70, color: "var(--range-2)", label: "Degraded" },
          { start: 70, end: 85, color: "var(--range-3)", label: "Fair" },
          { start: 85, end: 100, color: "var(--range-5)", label: "Healthy" },
        ]}
      />
    </div>
  )
}

Minimum segment width

Segments below minSegmentWidth get pinned to it; the deficit is redistributed across the rest. The pointer follows the actual rendered widths, so it always lands inside the correct segment. Accepts any CSS length or a number (px).

Without minimum width

0.2mIU/L
0.4425

With minimum width (default)

0.2mIU/L
0.4425
import { ReferenceRange } from "@/components/ui/reference-range"

import type {
  ReferenceRangeFirst,
  ReferenceRangeRest,
} from "@/components/ui/reference-range"

// TSH (thyroid-stimulating hormone) reference ranges. The "hyperthyroid"
// zone is just 0–0.4 mIU/L inside a clinically relevant 0–20 domain —
// roughly 2% of the bar. With proportional sizing it renders as a sliver
// that's hard to read and impossible to hover for the tooltip.
const ranges: [ReferenceRangeFirst, ...ReferenceRangeRest[]] = [
  { start: 0, end: 0.4, color: "var(--range-2)", label: "Hyperthyroid" },
  { end: 4, color: "var(--range-3)", label: "Normal" },
  { end: 25, color: "var(--range-4)", label: "Mild hypothyroid" },
  { end: 30, color: "var(--range-5)", label: "Severe hypothyroid" },
]

export default function MinSegmentWidthExample() {
  return (
    <div className="flex w-full max-w-md flex-col gap-6">
      <div>
        <p className="mb-3 text-sm">Without minimum width</p>
        <ReferenceRange
          value={0.2}
          unit="mIU/L"
          minSegmentWidth={0}
          ranges={ranges}
        />
      </div>
      <div>
        <p className="mb-3 text-sm">With minimum width (default)</p>
        <ReferenceRange value={0.2} unit="mIU/L" ranges={ranges} />
      </div>
    </div>
  )
}

Custom formatting

Use formatValue and formatTick to control how the pointer value and tick labels render.

API response time

1.24s
200ms700ms1s1.5s
import { ReferenceRange } from "@/components/ui/reference-range"

const formatMs = (v: number) => (v >= 1000 ? `${v / 1000}s` : `${v}ms`)

export default function CustomFormattingExample() {
  return (
    <div className="w-full max-w-md">
      <p className="mb-3 text-sm">API response time</p>
      <ReferenceRange
        value={1240}
        formatValue={formatMs}
        formatTick={formatMs}
        ranges={[
          { start: 0, end: 200, color: "var(--range-5)" },
          { start: 200, end: 700, color: "var(--range-4)" },
          { start: 700, end: 1000, color: "var(--range-3)" },
          { start: 1000, end: 1500, color: "var(--range-2)" },
          { start: 1500, end: null, color: "var(--range-1)" },
        ]}
      />
    </div>
  )
}

Custom pointer

Replace the default triangle via renderPointer. Receives { value, color, percent, range }.

Credit score

590
580670740800
import { ReferenceRange } from "@/components/ui/reference-range"

const fgByBg: Record<string, string> = {
  "var(--range-1)": "var(--range-1-fg)",
  "var(--range-3)": "var(--range-3-fg)",
  "var(--range-4)": "var(--range-4-fg)",
  "var(--range-5)": "var(--range-5-fg)",
}

export default function CustomPointerExample() {
  return (
    <div className="w-full max-w-md">
      <p className="mb-3 text-sm">Credit score</p>
      <ReferenceRange
        value={590}
        ranges={[
          { start: 300, end: 580, color: "var(--range-1)" },
          { start: 580, end: 670, color: "var(--range-3)" },
          { start: 670, end: 740, color: "var(--range-4)" },
          { start: 740, end: 800, color: "var(--range-5)" },
          { start: 800, end: 850, color: "var(--range-5)" },
        ]}
        renderPointer={({ value, color }) => (
          <div className="mb-2 flex flex-col items-center gap-[1px]">
            <span
              className="rounded-full px-2 py-0.5 text-xs font-semibold"
              style={{ backgroundColor: color, color: fgByBg[color] }}
            >
              {value}
            </span>
            <svg
              aria-hidden
              viewBox="0 0 14 11"
              preserveAspectRatio="none"
              className="block h-[16px] w-[20px]"
            >
              <path
                d="M4 2 L10 2 Q12 2 10.84 3.63 L8.16 7.37 Q7 9 5.84 7.37 L3.16 3.63 Q2 2 4 2 Z"
                fill={color}
              />
            </svg>
          </div>
        )}
      />
    </div>
  )
}

API Reference

ReferenceRange

ranges is an array of { start?, end, color, label? } entries with [start, end) boundaries (last range inclusive on both sides). Only the first range requires start; later ranges derive it from the previous end. If you do provide start on a later range and it doesn't match, the component throws a runtime error — gaps and overlaps are not silently rendered.

PropTypeDefaultDescription
ranges[ReferenceRangeFirst, ...ReferenceRangeRest[]]Ordered list of zones. Only the first range requires start (use null for open-ended); subsequent ranges derive start from the previous range's end — omit it. Providing a mismatched start throws a runtime error.
valuenumberThe current reading. Determines pointer position and color.
unitstringOptional unit shown next to the value above the pointer (e.g. "mg/dL").
distribution"proportional" | "equal""proportional"Proportional sizes each segment by its numeric width. Equal gives every segment the same width regardless of numeric range.
minSegmentWidthnumber | string"1.25rem"Floor for each segment's rendered width. Accepts any CSS length (rem, em, %, clamp(), etc.) or a number (px). Segments below the floor are pinned and the deficit is redistributed proportionally; the pointer is positioned against the actual rendered widths so it always lands inside the correct segment. Pass 0 to disable.
showValuebooleantrueShow the numeric value above the pointer triangle.
formatValue(v: number) => stringCustom formatter for the pointer value.
formatTick(v: number) => stringCustom formatter for tick labels under the bar.
tickLabels"boundaries" | "none" | number[]"boundaries"Show ticks at all defined inner boundaries (default), hide ticks entirely, or pass an explicit array of values to label.
renderPointer(ctx) => ReactNodeReplace the default triangle pointer. Receives { value, color, percent (0–100), range }.
ranges[].labelstringOptional zone label. Shown as a tooltip on hover; render it visually too if you want it always visible.
renderSegment(ctx) => ReactNodeReplace the default segment renderer. Receives { range, index, widthPercent, isOpenStart, isOpenEnd }.
classNamestringExtra classes merged onto the outer wrapper element.

Accessibility

  • The bar has role="img" with an aria-label announcing the value and unit — but not the zone. Render the active zone's name as text alongside the bar so screen readers and color-blind users get the severity, not just the number.
  • The pointer is aria-hidden — decorative.
  • label values appear as tooltips on hover (desktop only). Render them visually too if always-visible labels matter on touch devices.