Combo chart
FreeAn amount as bars and a rate as a line, on two axes. The bars rise first and the line draws over them; one column highlight reads both.
pnpm dlx shadcn@latest add https://caliperui.site/r/combo-chart.json| Period | Orders | Conversion |
|---|---|---|
| Jan | 820 | 2.1 |
| Feb | 760 | 2 |
| Mar | 910 | 2.3 |
| Apr | 1040 | 2.6 |
| May | 980 | 2.4 |
| Jun | 1210 | 2.9 |
| Jul | 1340 | 3.1 |
| Aug | 1290 | 2.8 |
| Sep | 1460 | 3.3 |
When to use #
Use it for
- Orders and conversion rate, signups and activation, spend and return.
- A rate that only means something next to its volume.
Skip it when
- Two amounts in the same unit; group them as bars.
- Readers who will compare the two axes' heights; say plainly that the scales differ.
Installation #
pnpm dlx shadcn@latest add https://caliperui.site/r/combo-chart.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.
import { ComboChart } from "@/components/ui/combo-chart" const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; const orders = months.slice(0, 9).map((label, i) => ({ id: label, label, values: { orders: [820, 760, 910, 1040, 980, 1210, 1340, 1290, 1460][i], rate: [2.1, 2.0, 2.3, 2.6, 2.4, 2.9, 3.1, 2.8, 3.3][i] } })); export function ComboChartDemo() { return ( <ComboChart label="Orders and checkout conversion" data={orders} bars={[{ id: "orders", label: "Orders" }]} line={{ id: "rate", label: "Conversion" }} formatBar={(v) => v.toLocaleString("en-US")} formatLine={(v) => `${v.toFixed(1)}%`} className="w-full" /> ); }
API reference #
ComboChart
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.
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.