caliper uidocs

Dumbbell chart

Free

A before and an after per row joined by a bar. The after dot travels out from the before value, and sorting by change moves rows to their new places.

Installs components/ui/dumbbell-chart.tsxDepends on d3-shapeMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/dumbbell-chart.json
Activation by onboarding step, before and after the new checklist
Invite a teammate+15%
Import data+7%
Connect bank−3%
First rule+15%
Share a report+3%
Activation by onboarding step, before and after the new checklist
RowAugustSeptemberChange
Invite a teammate314615
Import data54617
Connect bank4239-3
First rule223715
Share a report18213

When to use #

Use it for

  • Scores before and after a launch, prices last year and this.
  • Many rows, each with one change.

Skip it when

  • More than two moments; use the slope chart or a line chart.
  • A single row; write the two numbers.

Installation #

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

Adds its 7 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.

example.tsx
import { DumbbellChart } from "@/components/ui/dumbbell-chart"

export function DumbbellChartDemo() {
  return (
    <DumbbellChart
      label="Activation by onboarding step, before and after the new checklist"
      beforeLabel="August"
      afterLabel="September"
      unit="%"
      className="w-full"
      rows={[
        { id: "invite", label: "Invite a teammate", before: 31, after: 46 },
        { id: "import", label: "Import data", before: 54, after: 61 },
        { id: "connect", label: "Connect bank", before: 42, after: 39 },
        { id: "rule", label: "First rule", before: 22, after: 37 },
        { id: "report", label: "Share a report", before: 18, after: 21 },
      ]}
    />
  );
}

API reference #

DumbbellChart

component
PropTypeDefault
label *string—
rows *readonly DumbbellRow[]—
beforeLabelstring"Before"
afterLabelstring"After"
unitstring""
formatValue((value: number) => string)numberText
higherIsBetterbooleantrue
loadingboolean—
errorstring—
classNamestring—

Accessibility #

It exposes list and listitem semantics, with aria-label, aria-busy and aria-hidden 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.