Components
Split bar
One bar that is the whole, split into shares in the chart colours. Rest on a share, or focus its legend row, and it widens to show its figures while the others make room; switch shares off and the rest fill the bar again.
pnpm dlx shadcn@latest add @caliper/split-barWhen to use
Use it for
- A single breakdown that must fit in a row: revenue by plan, storage by type, time by project.
- Dashboards where a donut takes too much height.
Skip it when
- Comparing the same breakdown over time; use a stacked bar chart.
- More than six or seven shares; small ones become slivers.
Decisions
Why it is built this way, so you know what to keep when you change your copy.
Behaviour
- Focus widens
- The focused share gains an eighth of the bar on top of its size, so even a 2% sliver opens up enough to read its percentage.
- Percentages of what is left
- Switching a share off recomputes every percentage over the shares still shown, and the bar always keeps one.
Usage
- Legend is the keyboard path
- The bar is one image with every share in its name; the legend rows are toggle buttons that also focus their share.
Installation
pnpm dlx shadcn@latest add @caliper/split-bar
Adds its 2 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 { type ReactNode } from "react" import { SplitBar } from "@/components/ui/split-bar" function Panel({ title, context, className = "max-w-md", children }: { title: string; context: string; className?: string; children: ReactNode }) { return ( <section className={`grid w-full gap-5 rounded-overlay border border-border bg-background p-6 shadow-resting max-sm:p-4 ${className}`}> <div className="grid gap-1"> <p className="font-medium">{title}</p> <p className="text-sm text-muted-foreground">{context}</p> </div> {children} </section> ); } const plans = [ { id: "starter", label: "Starter", value: 18400 }, { id: "team", label: "Team", value: 52900 }, { id: "business", label: "Business", value: 71200 }, { id: "enterprise", label: "Enterprise", value: 38600 }, { id: "add-ons", label: "Add-ons", value: 6100 }, ]; export function SplitBarDemo() { return ( <Panel title="Monthly recurring revenue" context="October, by plan" className="max-w-lg"> <SplitBar label="Revenue by plan" segments={plans} format={(value) => `$${value.toLocaleString("en-US")}`} /> </Panel> ); }
API reference
SplitBar
component
Also accepts the native props of its root element and a ref.
Accessibility
It exposes img semantics, with aria-live, aria-label, aria-pressed, aria-disabled 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.