Skip to content
microcharts
ReferenceDecisionSpreadBand

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.

SpreadBand
interactive · 5.8 kB · static · 3.34 kB

Install

Import & usereact/spread-band
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 },];
Needs package + stylesheetSet up with AI

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

no gap label
down is good
locale

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

never crosses
identical series
gaps break both lines

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

PropTypeNotes
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).
animatebooleaninteractiveOpt-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