Box plot
FreeDistributions side by side as five-number summaries with outliers. Switching to every value spreads the points out from the median on the same axis.
pnpm dlx shadcn@latest add https://caliperui.site/r/box-plot.json| Group | Min | Q1 | Median | Q3 | Max | Outliers |
|---|---|---|---|---|---|---|
| Frankfurt | 78 | 127.5 | 146.5 | 183.25 | 245 | 11 |
| Virginia | 105 | 154.5 | 171.5 | 210.25 | 249 | 11 |
| Oregon | 119 | 174 | 205 | 249.25 | 362 | 10 |
| Singapore | 98 | 196 | 226 | 264.25 | 292 | 11 |
| São Paulo | 147 | 212.75 | 260.5 | 304.25 | 348 | 12 |
When to use #
Use it for
- Latency by region, deal size by segment, scores by cohort.
- Comparing spread as well as the middle.
Skip it when
- Readers new to quartiles; start with the histogram.
- Tiny groups; with under ten values show the points.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/box-plot.json
Adds its 6 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above. It is built from boxSummary and BoxPlot.
import { BoxPlot } from "@/components/ui/box-plot" const latencies = Array.from({ length: 600 }, (_, i) => { const a = Math.sin(i * 12.9898) * 43758.5453; const u = a - Math.floor(a); const b = Math.sin(i * 78.233) * 12345.678; const v = b - Math.floor(b); const normal = Math.sqrt(-2 * Math.log(u || 0.5)) * Math.cos(2 * Math.PI * v); return Math.max(40, Math.round((i % 7 === 0 ? 420 : 180) + normal * (i % 7 === 0 ? 60 : 38))); }); const regions = ["Frankfurt", "Virginia", "Oregon", "Singapore", "São Paulo"].map((label, r) => ({ id: label.toLowerCase(), label, values: latencies.slice(r * 80, r * 80 + 80).map((v) => Math.round(v * (0.8 + r * 0.14))), })); export function BoxPlotDemo() { return <BoxPlot label="p50 latency by region" groups={regions} unit=" ms" formatValue={(v) => `${Math.round(v)}`} className="w-full" />; }
API reference #
boxSummary
component
No props of its own; it accepts the native props of its root element.
BoxPlot
component
Accessibility #
It exposes group and status semantics, with aria-label, aria-busy and aria-hidden kept in step with state. Changes that matter are announced through a live region.
Motion #
It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.