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.
Preview
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.jsonColor 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
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
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
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
With minimum width (default)
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
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
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.
| Prop | Type | Default | Description |
|---|---|---|---|
| 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. |
| value | number | — | The current reading. Determines pointer position and color. |
| unit | string | — | Optional 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. |
| minSegmentWidth | number | 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. |
| showValue | boolean | true | Show the numeric value above the pointer triangle. |
| formatValue | (v: number) => string | — | Custom formatter for the pointer value. |
| formatTick | (v: number) => string | — | Custom 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) => ReactNode | — | Replace the default triangle pointer. Receives { value, color, percent (0–100), range }. |
| ranges[].label | string | — | Optional zone label. Shown as a tooltip on hover; render it visually too if you want it always visible. |
| renderSegment | (ctx) => ReactNode | — | Replace the default segment renderer. Receives { range, index, widthPercent, isOpenStart, isOpenEnd }. |
| className | string | — | Extra classes merged onto the outer wrapper element. |
Accessibility
- The bar has
role="img"with anaria-labelannouncing 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. labelvalues appear as tooltips on hover (desktop only). Render them visually too if always-visible labels matter on touch devices.