caliper uidocs

Bullet chart

Free

A KPI against its target in one line: the measure fills across poor, fair and good bands toward a target tick, with the gap written out.

Installs components/ui/bullet-chart.tsxDepends on d3-shapeMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/bullet-chart.json
Q3 goals
  • New ARR$k$412k of $450k · Behind target (−$38k)
  • Net revenue retention112% of 110% · On target (+2%)
  • Logo churn1.9% of 2.5% · On target (−0.6%)
  • API uptime99.93% of 99.95% · Behind target (−0.02%)

When to use #

Use it for

  • Quarterly goals, SLOs, budgets.
  • Several targets compared at a glance.

Skip it when

  • One value with no target; use the meter.
  • Progress through steps; use progress.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/bullet-chart.json

Adds its 3 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 bulletStatus and BulletChart.

example.tsx
import { BulletChart } from "@/components/ui/bullet-chart"

export function BulletChartDemo() {
  const money = (v: number) => `$${v.toLocaleString("en-US")}k`;
  return (
    <BulletChart
      label="Q3 goals"
      className="w-full max-w-2xl"
      items={[
        { id: "arr", label: "New ARR", detail: "$k", value: 412, target: 450, bands: [300, 400, 520], format: money },
        { id: "nrr", label: "Net revenue retention", value: 112, target: 110, bands: [95, 105, 125], format: (v) => `${v}%` },
        { id: "churn", label: "Logo churn", value: 1.9, target: 2.5, bands: [2, 3, 5], lowerIsBetter: true, format: (v) => `${v}%` },
        { id: "uptime", label: "API uptime", value: 99.93, target: 99.95, bands: [99.8, 99.9, 100], format: (v) => `${v}%` },
      ]}
    />
  );
}

API reference #

bulletStatus

component
PropTypeDefaultDescription
id *string—
label *string—
detailstring—e.g. "Revenue, $k".
value *number—
target *number—
bands *readonly [number, number, number]—Upper bounds of the poor, fair and good bands. The last one is the scale's maximum.
lowerIsBetterboolean—Lower is better, e.g. churn or latency.
format((value: number) => string)—

BulletChart

component
PropTypeDefault
label *string—
items *readonly BulletItem[]—
loadingboolean—
errorstring—
classNamestring—

Accessibility #

It exposes img semantics, with aria-label and aria-busy kept in step with state.

No custom key handling; Tab and the native activation keys do the work.

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.