PairedBars
React paired bars chart — two related magnitudes side by side from zero. Tiny accessible SVG microchart; RSC-safe static or /interactive.
PairedBars answers "how does actual compare to expected, category by category?". Value and reference always share one zero-anchored domain — comparing bars on different scales is the cardinal grouped-bar lie — and the reference is muted by two structural cues (opacity and width), never color alone.
Install
import { PairedBars } from "@microcharts/react/paired-bars";// regions — real values under “Sample data” below<PairedBars data={regions} title="Actual vs plan" />Sample data
const regions = [ { label: "East", value: 940, ref: 1200 }, { label: "West", value: 410, ref: 400 }, { label: "South", value: 620, ref: 600 }, { label: "North", value: 120, ref: 300 },];Try it
When to use it
- Good for — budget vs actual per region, target vs result rows.
- Avoid for — data without a reference series (MiniBar) or more than 5 pairs.
Sizing
Variants
The gap named in the summary follows format/locale — 12000 reads "12.000" under de-DE rather than the English
"12,000".
Edge cases
A pair whose ref is null draws its value bar alone — no ghost, no zero-width placeholder — and the interactive entry
announces it as "East: 940, no reference." rather than pretending a comparison exists. Past 5 pairs the grouped read
blurs into noise, so the component still renders every pair but logs a dev warning.
Four homes
Why this default
Grouped over overlay by default — overlap hides small over-shoots, so the denser overlay form is the variant you choose for tight cells, not the analytical default. A pair with a missing reference renders the value bar alone and announces "no reference"; a chart where every reference is missing dev-warns and steers to MiniBar.
Accessibility
The accessible name leads with the largest gap — "2 pairs. Largest gap East: 940 vs 1,200." The interactive entry roves pairs and announces each as value vs reference ("East: 940 vs 1,200.").
The interactive entry follows the shared interaction contract:
arrow keys rove between units on both axes, Home and End jump to the ends, and a click, tap, Enter or
Space selects a unit — pinning its readout so it survives blur, until you select it again or press Escape.
On touch, a tap pins and a drag scrubs.
Props
| Prop | Type | Notes |
|---|---|---|
| data* | { label; value; ref }[] | Value + reference per category. |
| mode | "grouped" | "overlay" | Overlay puts the ref as a full-width ghost behind — halves the footprint. |
| positive | "up" | "down" | Over/under-reference valence tint. |
| orientation | "horizontal" | "vertical" | Rows for wide cells. |
| locale | string | string[] | BCP 47 locale(s) for the gap named in the summary, e.g. "de-DE". |
| animate | boolean | interactiveOpt-in entrance motion when the chart mounts client-side — add import "@microcharts/react/motion" once. Inert on the server, on hydrated server HTML, and under prefers-reduced-motion. |
Plus the shared grammar — data, domain, color, title, summary, format — and the layout props (width, height, className, style) that every chart accepts. Interactive entries also share animate and live, and — wherever a chart has more than one navigable unit — onActive, onSelect, selectedIndex and defaultSelectedIndex; and — wherever the chart shows a hover value — readout. See the shared grammar.
Related charts
Dumbbell
React dumbbell chart — before/after or A/B pairs as dots linked on one scale. Tiny accessible SVG microchart; RSC-safe static or /interactive.
Slope
React slope chart — who rose and who fell between two moments, crossings readable at a glance. Tiny accessible SVG microchart; RSC-safe static or /interactive.