SpreadBand
React spread band chart — central tendency with a spread band around it. Tiny accessible SVG microchart; RSC-safe static or /interactive.
SpreadBand answers "which of two series leads, by how much, and since when?" The signed gap between a subject and its reference is filled and split at every crossing, so who is ahead and when it last flipped both read at a glance. The reference whispers: dashed, thinner, neutral — direction is carried by the fill sign and the text, never color alone.
Install
import { SpreadBand } from "@microcharts/react/spread-band";// pairs — real values under “Sample data” below<SpreadBand data={pairs} seriesLabels={["Organic", "Paid"]} title="Organic vs paid" />Sample data
const pairs = [ { a: 8, b: 12 }, { a: 9, b: 12 }, { a: 11, b: 13 }, { a: 12, b: 13 }, { a: 14, b: 13 }, { a: 15, b: 14 }, { a: 17, b: 14 }, { a: 18, b: 14 }, { a: 20, b: 15 }, { a: 21, b: 15 }, { a: 23, b: 16 }, { a: 24, b: 16 },];Try it
When to use it
- Good for — lead-vs-reference in KPI cards, actual-vs-plan where the flip matters.
- Avoid for — 3+ series (SparkGroup), or unpaired series / different units (never dual axes).
Sizing
Variants
The gutter label and the hover readout follow the locale's grouping — the German build reads the current gap as
+8.000 and the summary as "Organic leads Paid by 8.000; last crossed at point 5."
Edge cases
A null in either series is a gap in both — the gap is undefined where a reading is missing, so the fill and both
lines break at that index rather than inventing a value. Identical series read "The two series are level — no gap." and
a subject that never falls behind reports "never crossed."
Four homes
Why this default
One shared domain across both series — comparability is the whole point, so there are no dual axes and no per-series normalization. The gap is filled and split exactly at the interpolated crossings, so the eye lands on the flip without hunting. The reference whispers and the leader's sign is stated in text, so direction never rides on color alone.
Accessibility
The accessible name states the lead, the current gap, and where the lines last crossed — "Organic leads Paid by 8; last crossed at point 3." Identical series read "The two series are level — no gap." The interactive entry announces the lead at each point ("Point 12 of 12: Organic +8 over Paid.").
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* | { a: number | null; b: number | null }[] | Paired readings — a is the subject, b the reference. |
| seriesLabels | [string, string] | Names the two series in the summary and label. |
| positive | "up" | "down" | Which lead is the good valence; down flips the fill colors. |
| label | "gap" | "none" | Current signed gap in a right gutter (default gap). |
| 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
QueueDepth
React queue depth chart — backlog depth over time against a capacity hairline. Tiny accessible SVG microchart; RSC-safe static or /interactive.
PercentileTrace
React percentile trace chart — percentile fan or ribbon over ordered samples. Tiny accessible SVG microchart; RSC-safe static or /interactive.