caliper uidocs

Box plot

Free

Distributions side by side as five-number summaries with outliers. Switching to every value spreads the points out from the median on the same axis.

Installs components/ui/box-plot.tsxDepends on d3-shapeMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/box-plot.json
p50 latency by region
FrankfurtVirginiaOregonSingaporeSão Paulo

p50 latency by region
GroupMinQ1MedianQ3MaxOutliers
Frankfurt78127.5146.5183.2524511
Virginia105154.5171.5210.2524911
Oregon119174205249.2536210
Singapore98196226264.2529211
São Paulo147212.75260.5304.2534812

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 #

terminal
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.

example.tsx
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
PropTypeDefault
label *string—
groups *readonly BoxGroup[]—
unitstring""
formatValue((value: number) => string)numberText
view"points" | "box""box"
loadingboolean—
errorstring—
classNamestring—

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.

KeyBehaviour
↓Moves to the next item or value.
→Moves to the next item or value.
↑Moves to the previous item or value.
←Moves to the previous item or value.
EscapeCloses or cancels, then returns focus to where it came from.
HomeJumps to the first item or the minimum.
EndJumps to the last item or the maximum.

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.