s (hydration error). Expressions also survive
prettier's md/mdx prose-wrap, which re-expands a single line. */}
{[
{ name: "CPU", data: [62, 65, 61, 68, 70, 66, 72, 75], on: true },
{ name: "Memory", data: [48, 47, 49, 46, 45, 44, 43, 42], on: false },
{ name: "Network", data: [120, 118, 122, 119, 121, 120, 119, 118], on: false },
].map((m) => (
{m.name}
))}
```
## Charts sit on your surface [#charts-sit-on-your-surface]
Every chart is transparent and **never paints its own background**. It takes its ink color from `--mc-stroke`, which
tracks light and dark for you, and shows through to whatever surface it lands on. The contrast of that surface is your
decision.
* Put the chart on a surface with enough contrast for a 1.5 px line. The data, valence, and accent inks all clear 4.5:1
on the built-in light and dark themes. (`--mc-neutral`, the muted context ink for baselines and ghost marks, sits at
3.5:1, past the 3:1 bar for non-text and quieter than the data.)
* A low-contrast tint stays low-contrast: the chart does not compensate for it.
* Need the ink to differ from the page (charts on a colored panel, frosted glass)? Set `--mc-stroke`, and the valence
tokens if needed, on the container. See [Theming](/docs/theming#frosted-or-translucent-surfaces). Don't override
`.mc-root` paint rules.
> The gallery renders each demo inside a panel with a card background and a hairline border. That panel is not part of the library; it's a surface, like your app's cards, table rows, and headers.
## Small multiples: one shared scale [#small-multiples-one-shared-scale]
When you place several sparklines in a row to *compare* them, they must share one vertical scale. Auto-scaling each
chart to its own range makes a flat line and a spiking one look identical, which is a correctness bug and easy to miss.
`SparkGroup` handles it for you. It reads every child's `data`, computes **one** domain across all of them, and applies
**one** physical size, so the row is comparable. It's hook-free and RSC-safe, and a child's own explicit
`domain`/`width` still wins.
```tsx
` that sets `data-mc-theme` (the preset) plus any `tokens` as inline `--mc-*` custom
properties, identical to writing them by hand. `theme="modern"` writes no attribute, since modern *is* the base token
set. `theme` takes any preset (`modern`, `editorial`, `mono`, `vivid`, `dark`, plus the output-context `print` and
`eink`); `tokens` takes any `--mc-*` overrides. Both forms resolve to the same cascade, so use whichever reads better in
your code.
## Build a theme from one color [#build-a-theme-from-one-colour]
Setting a token or two by hand is the common case. For a whole brand theme, with a matched categorical palette and
hand-tuned dark twins for every color, use `defineTheme` from `@microcharts/react/theme`. Give it your brand accent and
it derives the rest in OKLCH: a harmonized, color-blind-safe categorical palette and a lifted dark-mode twin for each
token. It never moves the positive/negative hues off their CVD-safe green/vermillion split, so a derived theme encodes
direction the way the defaults do.
```tsx
import { defineTheme } from "@microcharts/react/theme";
import { MicroProvider } from "@microcharts/react";
const brand = defineTheme({ accent: "#6d28d9" });
// spread the tokens onto one scope…
;
// …or emit a global stylesheet — includes a prefers-color-scheme: dark block
const css = brand.css(":root");
```
One accent, a full derived palette. These are the values `defineTheme` returns:
```tsx
`"
>
```
The result carries everything you need for any delivery: `vars` (a `--mc-*` object), `darkVars` (the derived dark twins,
empty when `dark: false`), `style` (an alias of `vars`, ready to spread onto `MicroProvider` or any element),
`css(selector = ":root")` (a string with the `prefers-color-scheme: dark` block included), `toString()` (the same as
`css(":root")`), and `extend()` to spin a variant off an existing theme. Extend a preset, pin any token, or pass an
explicit palette when you don't want derivation:
```tsx
// extend a preset — the brand accent flows through
defineTheme({
extends: "editorial",
accent: "var(--brand-500)",
});
// bring your own categorical palette (no derivation)
defineTheme({
accent: "#6d28d9",
cat: ["#2563eb", "#db2777", "#65a30d"],
});
// derive a specific number of categorical tones instead of the default six
defineTheme({ accent: "#6d28d9", cat: 3 });
// opt out of accent-seeded derivation, or out of dark twins entirely
defineTheme({ accent: "#6d28d9", derive: false });
defineTheme({ accent: "#6d28d9", dark: false });
// override just some dark twins (the rest stay auto-derived)
defineTheme({ accent: "#6d28d9", dark: { accent: "#a78bfa" } });
// compose a compact variant off an existing theme
defineTheme({ density: 0.85 }).extend({ labelWeight: 500 });
// pin geometry directly — strokeWidth, gap, labelSize, labelWeight, density
defineTheme({ accent: "#6d28d9", strokeWidth: 2 });
```
It's pure, dependency-free, and tree-shaken out of any bundle that never imports it.
## Density [#density]
`--mc-density` is a single scalar that scales stroke weight, label size, and small-multiple gap together: one knob for
compact tables (`< 1`) or an airier standalone layout (`> 1`). It tunes the ink and type, not the box. The plot size
still comes from `width`/`height`, so nothing reflows.
```css
/* a denser table of sparklines */
.metrics-table {
--mc-density: 0.85;
}
```
One asymmetry: **stroke and gap scale without limit, but the label stops scaling at 1.25.** Direct labels sit in a
gutter the chart reserves from a character-count estimate while it draws, server-side, where text cannot be measured.
That reservation is fixed at render time and cannot see a CSS variable set later, so past about 1.25 the text outgrows
the space held for it and paints outside the chart. Below `1` there is no such limit: the label shrinks inside a gutter
already reserved for something bigger, which is safe.
So `--mc-density: 2` gives you double-weight strokes and double gaps, with labels at 1.25×. For larger text, scale the
chart with `width`/`height` or `--mc-label-size` instead of leaning on density.
## The font [#the-font]
`--mc-font` defaults to `inherit`, so charts adopt the font of whatever contains them. If the surrounding page sets a
`font-family`, charts follow it with no configuration. If it doesn't, as in some minimal starters and code sandboxes,
SVG text falls back to the browser default (usually a serif) and labels come out looking wrong.
Two fixes, either works: give your page a font the normal way, or point `--mc-font` at a stack for the charts alone.
```css
:root {
/* charts only — the rest of the page is untouched */
--mc-font: system-ui, sans-serif;
}
```
Numbers always render with `tabular-nums`, so columns of figures stay aligned in any font. Want the figures in a
dedicated face, a tabular numeric font or a mono for a data-dense KPI, without changing the label font? Point
`--mc-font-numeric` at it. It defaults to `--mc-font`, so leaving it alone keeps everything on one typeface.
`--mc-label-weight` (default `400`) sets label weight the same way.
```css
:root {
--mc-font: Inter, sans-serif; /* labels */
--mc-font-numeric: "IBM Plex Mono", monospace; /* figures */
}
```
## The readout surface [#the-readout-surface]
The one opaque plane in the library is the value chip an interactive chart shows for its active unit: under the pointer,
under keyboard focus, or pinned in place after a click, tap, or **Enter**. Its three `--mc-surface*` tokens default to
the system `Canvas`, so the chip adapts to light, dark, and forced-colors with no configuration. Point them at your
popover tokens to match a themed surface. One set of tokens covers the hovered, focused, and pinned states alike.
## Dark mode and forced colors [#dark-mode-and-forced-colors]
Dark values are hand-tuned per token, never auto-inverted, so overriding one light value can't break the dark surface.
microcharts follows `prefers-color-scheme` by default, and you can force a scope with `data-mc-theme="dark"`. Charts
never paint their own background, so they sit on whatever surface you give them.
If your app toggles theme with a **class** (next-themes `.dark`, a `[data-theme]` attribute, …) rather than the OS media
query, rebind the tokens under that class the same way you bind accent — the library's media-query defaults are
`:where()` zero-specificity, so your class rules win:
```css
:root {
--mc-accent: var(--brand);
}
.dark {
--mc-stroke: #eae9e6;
--mc-positive: #45a385;
--mc-negative: #df7856;
--mc-accent: var(--brand);
/* …or data-mc-theme="dark" on a scope, if you prefer the shipped dark bundle */
}
```
### Frosted or translucent surfaces [#frosted-or-translucent-surfaces]
Default valence inks clear 4.5:1 on opaque white and near-black. On a frosted-glass or tinted panel the composited
backdrop is quieter, so deepen `--mc-stroke`, `--mc-positive`, `--mc-negative`, and `--mc-neutral` on that surface until
contrast holds. Rebind the tokens rather than patching chart CSS. (This docs site does that under `:root` and `.dark` so
marks stay legible on glass.)
Under Windows High Contrast (`forced-colors`), the semantic tokens defer to system colors: ink to `CanvasText`, accent
to `Highlight`, muted marks to `GrayText`. A chart stays legible even when your palette is overridden entirely.
`prefers-contrast: more` is handled too: charts bump `--mc-stroke-width` to `2` and deepen `--mc-band` from an 8% to a
16% mix of the ink, so lines and normal-range shading both gain weight without you configuring anything.
# Troubleshooting (/docs/troubleshooting)
Most issues trace back to one of a handful of causes. Each fix is below, with a link to the guide page that explains it
in full.
## Charts render with no color, no strokes, just plain shapes [#charts-render-with-no-color-no-strokes-just-plain-shapes]
The stylesheet isn't loaded. Every visual property comes from `styles.css`: color, stroke weight, label size, the whole
theme. Import it once:
```tsx
import "@microcharts/react/styles.css";
```
Without it a chart is still valid, accessible SVG: the markup and the accessible name are both there, with no styling
applied. See [Quickstart](/docs/quickstart#compatibility) for where the import goes in your framework.
## A chart is invisible (or nearly) on a colored or dark panel [#a-chart-is-invisible-or-nearly-on-a-colored-or-dark-panel]
Charts never paint their own background; they render transparent SVG. On a plain light or dark page you never notice,
because the built-in light and dark tokens track `prefers-color-scheme`. It breaks when a chart sits on a surface whose
color doesn't match the *page's* color scheme: a dark-branded card on an otherwise light page, or a colored KPI tile.
The tokens don't know that scope looks dark; they only know what the OS or browser reports.
Fix it by rebinding the token scope to match the surface, not the page:
```css
.dark-panel {
--mc-stroke: #f5f5f5; /* text + data strokes; add --mc-neutral for muted labels */
}
```
or force the whole preset for that scope:
```tsx
```
See [Theming](/docs/theming) for the full token set and how scope, preset, and prop precedence works.
## `Error: ... 'use client' ...` when importing a chart [#error--use-client--when-importing-a-chart]
You imported a chart's `/interactive` entry into a Server Component:
```tsx
// ❌ interactive entries are client components — this throws in an RSC
import { Sparkline } from "@microcharts/react/sparkline/interactive";
```
Two ways out. Render the static default entry if you don't need hover, keyboard, touch, or selection:
```tsx
import { Sparkline } from "@microcharts/react/sparkline";
```
Or, if you do need interactivity, move the import into a component marked `"use client"` and render that component from
your server tree. See [Quickstart](/docs/quickstart#add-interactivity) for the static-vs-interactive split.
## `onPointFocus` / `onRunFocus` no longer fire [#onpointfocus--onrunfocus-no-longer-fire]
They were removed when every interactive chart moved onto one shared contract. The replacement is `onActive`, which
fires for the same reason on every chart in the catalog:
```tsx
// ❌ removed
setHovered(i)} />
// ✅ one name, everywhere
setHovered(d?.index ?? null)} />
```
The payload is a `MicroDatum`, `{ index, value, label?, formatted? }`, where `value` is the raw number and `formatted`
is the chart's ready-to-display string; it's `null` when the chart clears. `onPointFocus` on Sparkline and SparkBar and
StreakSpark's `onRunFocus` map across directly. `index` identifies the chart's *navigable unit*, which isn't always a
series index: on Waveform it's a bucket, on Hypnogram a run, on SegmentedBar a segment. Each chart's page names its own
unit. For click-to-pin, add `onSelect`; see [Accessibility](/docs/accessibility#one-interaction-contract).
## `animate` does nothing [#animate-does-nothing]
The entrance engine isn't loaded. `animate` is a flag; the code that runs the animation ships in a separate side-effect
import you add once, anywhere in your client bundle:
```tsx
import "@microcharts/react/motion";
```
In development the chart logs this to the console when the engine is missing. Three other reasons `animate` is inert,
none of them bugs: the reader has `prefers-reduced-motion: reduce`, the chart is a static entry (`animate` is an
`/interactive`-only prop), or the markup came from the server and is being hydrated rather than freshly mounted, where
an entrance would animate content the reader can already see. The entrance runs once per mount by design; remount with a
changed `key` to replay it.
## The chart won't stretch to fill its container [#the-chart-wont-stretch-to-fill-its-container]
`.mc-root`, the chart's own ``, is `display: inline-block` by default, so it flows next to text instead of
stretching. Give it fluid sizing explicitly:
```tsx
```
and make sure the *parent* is block-level (a flex or grid item, or a `div`). A chart can't grow past an inline parent no
matter what you set on the chart itself. Interactive entries behave identically: `style`, `className`, and
`width`/`height` size an interactive chart exactly the way they size its static twin, and hit-testing stays exact at any
size, so the crosshair tracks the cursor and a touch drag scrubs the unit under the finger. Full recipes, including the
inline-in-a-sentence case, are on [Sizing & scaling](/docs/sizing).
## A chart stacks on its own line instead of sitting inline with text [#a-chart-stacks-on-its-own-line-instead-of-sitting-inline-with-text]
This is usually a CSS reset, not a microcharts bug. `.mc-root`'s `display: inline-block` is declared inside a
zero-specificity `:where()` so your styles always win, including ones you didn't mean to apply. Tailwind's Preflight
sets a plain `svg { display: block; ... }` rule, which has *higher* specificity than any `:where()` selector and
overrides it, dropping the chart onto its own block-level line. Re-assert the layout you want at the call site:
```tsx
```
or wrap it in `className="mc-inline"` for text-flow placement — see
[Sizing & scaling](/docs/sizing#inline-in-a-sentence).
## Chart labels look wrong — a serif fallback, or mismatched with the page [#chart-labels-look-wrong--a-serif-fallback-or-mismatched-with-the-page]
Charts inherit `font-family` from the page; they don't set one of their own. If the surrounding page never declares a
font (a bare sandbox, a minimal starter), SVG text falls back to the browser's default serif. See
[Theming → The font](/docs/theming#the-font) for why it inherits and how to set it per-scope.
# microcharts vs Chart.js for inline charts (/docs/vs-chartjs)
[Chart.js](https://www.chartjs.org) is a Canvas-first chart library with a large plugin ecosystem, usually wrapped by
[`react-chartjs-2`](https://react-chartjs-2.js.org) in React apps. microcharts renders SVG. Almost every difference
below follows from that one difference, so this page is organized around the renderer rather than a feature list.
A dense analyst dashboard or a report full of chart panels is Chart.js territory. The comparison here covers one narrow
job: a chart the size of a word, inside a sentence, a table cell, or a KPI card. Broader positioning lives in
[When to use microcharts](/docs/when-to-use) and [Full chart libraries](/docs/full-chart-libraries).
## The numbers [#the-numbers]
microcharts numbers come from `.size-limit.json`, the CI gate, re-measured on every build. The Chart.js figures are
pinned with version and date; they are dated orientation rather than live measurements.
## What the canvas decides [#what-the-canvas-decides]
**There is no markup.** A canvas only exists in the browser, so there is no static-HTML output path and the chart
appears after JavaScript runs. microcharts renders SVG, so the mark is real DOM: it exists in the server-rendered HTML,
prints correctly, and scales crisply at any zoom.
**It hydrates.** `react-chartjs-2` manages the canvas with hooks and effects, so it runs in a client boundary and ships
Chart.js to the browser. The microcharts default export is hook-free static SVG, and an RSC can render it with zero
client JavaScript.
**It is a bitmap to assistive technology.** The Chart.js docs recommend supplying your own fallback content or ARIA
labeling per chart, and plugins exist to help. That path works; it is manual, once per chart. Every microchart defaults
to `role="img"` plus a natural-language summary generated from the data.
**It repaints through JavaScript.** Inline marks sit next to your text, so they inherit its rhythm. SVG takes CSS custom
properties, `currentColor`, and container-relative sizing directly. A canvas has to redraw when the theme flips.
## When Canvas is the right renderer [#when-canvas-is-the-right-renderer]
* Dashboard or report canvases with many series and heavy point counts. Above a few thousand points, canvas usually wins
* You already run Chart.js configs or depend on its plugin ecosystem
* Canvas rendering is a requirement, not an implementation detail
## When the mark is inline [#when-the-mark-is-inline]
* It lives in prose, cells, tabs, KPI cards, or streamed AI replies
* You want the chart present in server-rendered HTML, with zero client JS by default
* You want an accessible name without per-chart wiring
* Per-mark budget: gzip, zero runtime dependencies
## Next [#next]
* [Full chart libraries](/docs/full-chart-libraries) — Recharts and Chart.js in one view
* [microcharts vs Recharts](/docs/vs-recharts)
* [When to use microcharts](/docs/when-to-use)
* [Accessibility](/docs/accessibility) — what the defaults do
# microcharts vs MUI X Sparkline — bundle size and RSC comparison (/docs/vs-mui-x-sparkline)
[MUI X Charts](https://mui.com/x/react-charts/) ships `SparkLineChart`, an inline-sized chart from the same family as
its full Line, Bar, and Pie charts, themed by and integrated with Material UI. This comparison turns on one question:
whether your app already ships MUI. Answer it first, because it changes which library is the cheap one. The question
bites hardest where the mark must be cheap, static, or independent of MUI: a table column of trends, a KPI row in an
RSC, a chart in a streamed AI reply.
## The numbers [#the-numbers]
The two MUI scenarios are deliberate. Inside a MUI app the Material core is already in your bundle, so the sparkline's
marginal cost is the smaller figure. Without MUI, `@mui/material` and `@mui/system` are required peer dependencies: you
install and ship them to render one sparkline. MUI X Charts (community) is MIT and actively maintained, and these sizes
are dated pins from the published package.
## If your app is already on MUI [#if-your-app-is-already-on-mui]
`SparkLineChart` is the coherent choice, and this page may talk you out of nothing. You get one vendor for the sparkline
and the full charts beside it, the same theme object driving both, and the MUI X tooltip, highlighting, and animation
stack you already use elsewhere. The shared charting core (data providers, interaction plugins, vendored d3) is what
makes that family consistent, and it is why one sparkline carries tens of kilobytes.
Two defaults are worth checking even there:
* `SparkLineChart` is marked `'use client'`. The server can emit initial HTML, but the component always hydrates and
ships its JavaScript.
* As published, the `SparkLineChart` SVG renders with `aria-hidden="true"` and no accessible name, so the chart is
invisible to assistive technology until you label it yourself.
## If it is not on MUI [#if-it-is-not-on-mui]
The mark then has to justify pulling in MUI, and microcharts is the cheaper answer on four counts:
* **Bundle.** microcharts charges per chart subpath: for Sparkline against a catalog band
of gzip, with zero runtime dependencies and no required peers beyond React.
* **Server Components.** The default export is hook-free static SVG with zero client JS; interactivity is a separate
`/interactive` import.
* **Accessibility.** Every microchart defaults to `role="img"` with a natural-language summary generated from the data,
so labeling is not a task per chart.
* **Coverage.** word-sized chart types under one grammar, when you need more shapes than a sparkline.
Theming crosses both cases. MUI X charts theme through the MUI theme object, which is coherent if you live there.
microcharts themes through `--mc-*` CSS custom properties at near-zero specificity, so it follows any design system,
including a MUI one.
## Next [#next]
* [When to use microcharts](/docs/when-to-use)
* [microcharts vs Recharts](/docs/vs-recharts)
* [React sparklines](/docs/react-sparklines)
* [Theming](/docs/theming) — fitting charts into an existing design system
# microcharts vs react-sparklines — React sparkline comparison (/docs/vs-react-sparklines)
[`react-sparklines`](https://www.npmjs.com/package/react-sparklines) put sparklines into thousands of React apps and
still gets massive weekly downloads. It is the package many "react sparkline" searches land on, so if you are choosing
between it and microcharts, this page is the factual side-by-side. The package earned its adoption; what follows is what
has changed since it was written.
## The numbers [#the-numbers]
## The same chart in both [#the-same-chart-in-both]
The shapes map directly:
```tsx
// react-sparklines
import { Sparklines, SparklinesLine } from "react-sparklines";
;
// microcharts
import { Sparkline } from "@microcharts/react/sparkline";
;
```
`title` gives the chart its accessible subject; the summary is generated from the data. Bars map to
[SparkBar](/docs/charts/sparkbar), reference lines to [``](/docs/annotations).
## What changed since 2017 [#what-changed-since-2017]
**Maintenance.** `react-sparklines` last published in 2017 (`1.7.0`). It predates hooks and Server Components, so
nothing from that era can be expected to target them. microcharts is actively maintained against React 18 and 19, with
StrictMode-safe rendering tested in CI.
**Accessibility.** `react-sparklines` renders a bare ``: no `role`, no ``, no accessible name, so a screen
reader finds nothing. Every microchart is `role="img"` with a natural-language summary generated from the data
(`describeSeries`), and interactive entries add keyboard navigation with a polite live region.
**Server Components.** `react-sparklines` components are class components, which need a client boundary in an App Router
app. The microcharts default export is hook-free static SVG that renders directly in an RSC with zero client JavaScript.
## Scope and size [#scope-and-size]
microcharts is a catalog of word-sized chart types under one grammar: `data` alone renders something
correct, and the same prop names mean the same thing on every chart. Sparkline is one entry in it. `react-sparklines` is
one visual idea with composable overlays.
Both are small. `react-sparklines` is a single \~7.9 kB gzip package; microcharts charges per chart subpath
( for Sparkline) with a CI gate that keeps every chart inside its budget.
## When react-sparklines is still fine [#when-react-sparklines-is-still-fine]
* A legacy codebase already uses it, the charts are decorative, and nothing is broken
* You need its exact composable-overlay API and have your own accessibility layer
There is no urgency to migrate in those cases. The gaps above matter when accessibility, RSC, or maintenance start to
cost you something.
## Next [#next]
* [React sparklines](/docs/react-sparklines) — the mark itself, in depth
* [Sparkline API](/docs/charts/sparkline)
* [When to use microcharts](/docs/when-to-use)
* [Accessibility](/docs/accessibility)
# microcharts vs Recharts — inline charts comparison (/docs/vs-recharts)
[Recharts](https://recharts.org) is a full chart library, and microcharts does not replace it. If your page *is* the
chart, with axes, a legend, a tooltip, and a brush, use Recharts. This page compares the two for one job: a word-sized
chart inside an interface, where the surface is mostly words and UI.
The broader positioning lives in [When to use microcharts](/docs/when-to-use) and
[Full chart libraries](/docs/full-chart-libraries). What follows is the measured detail for the Recharts decision.
## The numbers [#the-numbers]
Both microcharts columns come from `.size-limit.json`, the CI gate that fails the build when a chart grows past its
budget. The Recharts figures are pinned with version, method, and date. They are dated orientation rather than live
measurements, and they will age.
## What differs [#what-differs]
**Bundle cost per mark.** A tree-shaken Recharts `LineChart` carries the shared Recharts core. That cost is fine
amortized over a dashboard, and hard to justify for one sparkline in a table header. microcharts imports one chart per
subpath, so a page that uses only `Sparkline` pays only for `Sparkline`.
**Server Components.** Recharts components are hook-based, so in an App Router app they run inside a client boundary and
ship their JavaScript. The microcharts default export is hook-free static SVG: it renders in an RSC with zero client
JavaScript, and interactivity is a separate opt-in `/interactive` import.
**Accessibility defaults.** Recharts ships an accessibility layer, on by default in v3, with keyboard navigation and
ARIA roles on the chart surface. A natural-language description of the data is still yours to write. Every microchart is
`role="img"` with that description generated from the data by default (`describeSeries`); the decorative opt-out is
explicit (`summary={false}`).
**Chart chrome.** Recharts draws axes, ticks, legends, and tooltips because dashboard surfaces need them, and it is
sized correctly for that job. microcharts hard-codes them away because word-sized marks cannot afford them. The two
libraries serve different surfaces.
## Reach for Recharts [#reach-for-recharts]
* The page is mostly chart: analytics views, report panels, explorable dashboards
* You need ticks, legends, brush, zoom, or a tooltip that follows the pointer
* Your team already has a Recharts component tree it trusts
## Reach for microcharts [#reach-for-microcharts]
* The mark sits in a sentence, a table cell, a tab, a KPI card, or a streamed AI reply
* You want static chart SVG out of a React Server Component with nothing to hydrate
* You want an accessible name generated from the data without wiring it per chart
* Budget matters per mark: gzip, zero runtime dependencies
## Same app, both libraries [#same-app-both-libraries]
Most apps that need both end up here. Recharts on the analytics page, microcharts in the table that links to it:
```tsx
// analytics page — Recharts
…
;
// the table that links to it — microcharts
import { Sparkline } from "@microcharts/react/sparkline";
;
```
## Next [#next]
* [Full chart libraries](/docs/full-chart-libraries) — Recharts and Chart.js in one view
* [When to use microcharts](/docs/when-to-use) — the product decision
* [microcharts vs Chart.js](/docs/vs-chartjs)
* [Performance](/docs/performance) — our own CI receipts
# microcharts vs visx — tiny charts comparison (/docs/vs-visx)
[visx](https://airbnb.io/visx/) is Airbnb's collection of low-level visualization primitives: shapes, scales, axes, and
gradients you compose into your own charts. It is deliberately not a chart library. You are the chart library.
microcharts makes the opposite bet: finished, opinionated word-sized marks with the design decisions already made.
So this is a fork in the road rather than a competition. Both keep bundles lean through per-package (visx) or per-chart
(microcharts) imports. What you are choosing is who does the chart design, you or the library. The sections below list
what each choice makes you responsible for.
## The numbers [#the-numbers]
The visx figure is a floor: `LinePath` plus one scale, before curves, tooltips, or axes. It is a dated pin for that one
minimal composition, not a benchmark of visx overall, and 16 kB is a reasonable floor for hand-built charts. Each
`@visx/*` package you add brings its own dependencies (d3 arrives vendored through `@visx/vendor`).
## Three things you own with visx [#three-things-you-own-with-visx]
**The design.** You decide geometry, padding, label placement, empty-data behavior, negative-value handling, and color
semantics per chart. That is full control, and a real design task at 16 pixels tall. microcharts hard-codes those
decisions: areas anchor at zero, direction is never color-alone, labels reserve deterministic gutters, empty and
all-null data render documented placeholders.
**Accessibility.** A visx `LinePath` renders a bare ``, so `role`, ``, and a description of the data are
yours to author on every chart you build. Every microchart defaults to `role="img"` with a natural-language summary
generated from the data.
**Edge cases.** NaN, ±Infinity, single points, all-equal series: with primitives, each is your code path. Every
microchart documents and tests these in a shared fixture suite.
## Dependencies [#dependencies]
visx keeps React in control of the DOM and slices d3 into per-package imports, which is genuinely lighter than shipping
all of d3, and its modularity is well executed. microcharts goes further for its narrower job: scales, paths, easing,
color, and summaries are in-house, so `dependencies: {}` is a CI-enforced invariant.
## Build it yourself with visx [#build-it-yourself-with-visx]
A bespoke visualization deserves primitives, so reach for visx when:
* The visualization is bespoke and no catalog anywhere has your chart
* You have design capacity and want d3's power with React's rendering
* You are building your organization's own charting layer, at any surface size
## Take the catalog with microcharts [#take-the-catalog-with-microcharts]
* The chart you need already exists: word-sized types, one grammar
* You want accessibility, edge cases, and design handled by default
* You want static RSC output with zero client JavaScript, at gzip per chart
## Next [#next]
* [When to use microcharts](/docs/when-to-use)
* [Design notes](/docs/design-notes) — the decisions microcharts makes for you
* [microcharts vs Recharts](/docs/vs-recharts)
* [Composition](/docs/composition) — placing marks in real surfaces
# When to use microcharts (/docs/when-to-use)
microcharts draws tiny charts for React that sit *inside* an interface: a trend after a number, a bullet in a KPI, a
strip in a table cell, a mark in a streamed reply.
It is not a substitute for [Recharts](https://recharts.org), [Chart.js](https://www.chartjs.org), Nivo, ECharts, or
visx. Those libraries draw surfaces that are mostly chart: axes, legends, tooltips, brush, zoom. microcharts draws
mostly words and UI with a word-sized mark in them. Use a full library when the page *is* the chart. Use microcharts
when the chart sits inside something you were already reading.
## Fit [#fit]
| Situation | Fit |
| ------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| Sparkline, bullet, or delta in prose, a cell, a tab, or a card | **microcharts** |
| Static chart from a React Server Component with **zero** client chart JS | **microcharts** (default export) |
| A model or agent emitting a chart mid-reply | **microcharts** — see [AI-native](/docs/ai) |
| Full Line / Bar / Area with ticks, legend, tooltip, brush | **Recharts, Chart.js, …** |
| Dense Canvas series, Chart.js plugins, analyst dashboards | **Chart.js** (or similar) |
| One library for every chart in the product | Usually a **full** library; add microcharts for the inline marks |
## Same app, two jobs [#same-app-two-jobs]
Most products need both rows of that table. Use Recharts on the analytics page and a Sparkline in the table that links
to it — different import, different budget:
```tsx
// dashboard panel — your full chart library
… ;
// same product, table cell — microcharts
import { Sparkline } from "@microcharts/react/sparkline";
;
```
## Size [#size]
Every chart is imported from its own subpath and budget-gated in CI: gzip, **zero runtime
dependencies**, static SVG from an RSC with nothing to hydrate. Each one is `role="img"` with a summary generated from
the data.
Pinned orientation numbers for Recharts and Chart.js (version, date, method):
[Full chart libraries](/docs/full-chart-libraries). Our own receipts: [Performance](/docs/performance).
## What the catalog leaves out [#what-the-catalog-leaves-out]
Pie, needle gauges, waffles, and similar shapes fail at word size, so they're excluded on purpose. Each has an
in-catalog replacement — for example [SegmentedBar](/docs/charts/segmented-bar), [Bullet](/docs/charts/bullet), and
[IconArray](/docs/charts/icon-array). The reasoning is in [Design notes](/docs/design-notes#what-we-dont-ship).
## Go deeper [#go-deeper]
* [React sparklines](/docs/react-sparklines) — the most common word-sized mark
* [Inline charts](/docs/inline-charts) — sentences, cells, KPIs, tabs, RSC
* [Full chart libraries](/docs/full-chart-libraries) — Recharts and Chart.js, side by side
* Comparing a specific library? [vs Recharts](/docs/vs-recharts) · [vs Chart.js](/docs/vs-chartjs) ·
[vs react-sparklines](/docs/vs-react-sparklines) · [vs MUI X Sparkline](/docs/vs-mui-x-sparkline) ·
[vs visx](/docs/vs-visx)
* [Quickstart](/docs/quickstart) — install and first chart
* [Introduction](/docs) — grammar, catalog, and where they live
# ABStrips (/docs/charts/ab-strips)
ABStrips takes two samples of raw values, a control arm and a test arm, and draws **two graded quantile strips on one
shared scale**: a faint p5–95 band, a stronger p25–75 middle half, and a median dot, with row A muted and row B accent.
Whether the two middle halves still overlap is the read: while they do, the gap between the medians isn't a result yet.
The overlap number is always in the summary, because a delta without its spread doesn't say whether the two arms differ.
The strips are mandatory context: there is no bare-mean-bar mode, and the delta label never renders without the
distributions behind it.
```tsx
```
## Install [#install]
```tsx
import { ABStrips } from "@microcharts/react/ab-strips";
const control = Array.from({ length: 80 }, (_, i) => 130 + ((i * 13) % 44) - 22);
const test = Array.from({ length: 80 }, (_, i) => 116 + ((i * 13) % 44) - 22);
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { ABStrips } from "@microcharts/react/ab-strips/interactive";
```
## When to use it [#when-to-use-it]
Use it for an A/B experiment result in a KPI card, control vs test in an experiments table, and any two-sample
comparison where the spread matters. For a single distribution use BenchmarkStrip; for more than two arms use small
multiples.
## Sizing [#sizing]
**labelled arms**
```tsx
```
**clearly separated**
```tsx
```
## Variants [#variants]
```tsx
130 + ((i * 13) % 44) - 22);
const test = Array.from({ length: 80 }, (_, i) => 116 + ((i * 13) % 44) - 22);
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
Either arm with zero finite values makes the comparison unanswerable: no strips draw, and the accessible name reads
**"No data."** An arm with fewer than 8 points still draws. Only its outer band changes meaning, from the 5th–95th
percentile to the plain min–max, because tail percentiles need more than a handful of points to estimate.
## Four homes [#four-homes]
**In a sentence**
```tsx
Latency A/B this week{" "}
{" "}
— variant B wins by 12 ms on median.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
```
**In a tab header**
```tsx
checkout
```
## Accessibility [#accessibility]
The accessible name states both medians, the delta, and the overlap — **"Test median 115 ms vs Ctrl 129 ms (-11%);
middle halves overlap 35%."**. When the middle halves fully overlap the summary says "no clear difference"; when they
are disjoint it says "clearly separated". The interactive entry roves the rows (↑/↓) and the quantile edges (←/→): the
median announces the delta vs the other arm, other edges announce the percentile.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ a: number[]; b: number[] }` | The two arms — raw samples, not summaries. |
| `seriesLabels` | `[string, string]` | Row identities for the gutter tags + summary (default ['A', 'B']). |
| `positive` | `"up" \| "down"` | Which direction of the B−A delta reads as good (colors the delta). |
| `label` | `"delta" \| "none"` | Signed median delta in a right gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# ActivityGrid (/docs/charts/activity-grid)
Each cell is one slot in the series, shaded by intensity: the contribution-graph shape, read for cadence, streaks, and
seasonality. There are five levels — a faint empty track plus four intensity steps — and the four steps share the exact
opacity ramp HeatCell and HeatStrip use, so "how intense" means the same thing across the library. Level 0 is the empty
track, because a slot with no activity is empty rather than a bottom-of-scale value. The steps are discrete bins, never
a continuous gradient: a continuous ramp implies precision a handful of pixels can't deliver.
```tsx
`"
>
```
## Install [#install]
```tsx
import { ActivityGrid } from "@microcharts/react/activity-grid";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { ActivityGrid } from "@microcharts/react/activity-grid/interactive";
```
## When to use it [#when-to-use-it]
Use it for daily activity, streaks and cadence, and seasonality. Avoid it for exact values or for a precise comparison
between two individual cells: color intensity is deliberately approximate, so pair it with a number when precision
matters.
## Sizing [#sizing]
ActivityGrid sizes from `cell` — the edge length of one square in viewBox units. Bump it to scale the whole grid, or
drive the width from CSS to fill a container; the viewBox keeps the grid's aspect ratio.
**default cells**
```tsx
// ActivityGrid sizes from cell edge length (default 10)
```
**larger cells**
```tsx
// bump every cell — the whole grid scales with it
```
**responsive**
```tsx
// let CSS drive the width — the viewBox keeps the grid's ratio
```
## Variants [#variants]
`layout="strip"` collapses to a single row for inline use.
```tsx
`"
>
```
`shape` swaps the cell mark without changing the data or the levels. `"round"` softens the grid for product surfaces;
`"dot"` adds space between cells in dense strips.
```tsx
`"
>
```
`anchor` aligns the grid to the real calendar: the first column pads down to the weekday of the given day (UTC), so rows
read as weekdays. Weeks start on Monday by default (`weekStart={1}`, matching the ISO calendar); `weekStart={0}`
switches to Sunday without touching the data. Cells fill column-major — each column is a week, top to bottom — which is
what makes the grid read as a calendar rather than an arbitrary sequence.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
## Four homes [#four-homes]
**In a sentence**
```tsx
Commit cadence for the last two weeks{" "}
{" "}
— bursts of shipping, punctuated by two dead-quiet days.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
67
across 35 days, busiest at 4
```
**In a tab header**
```tsx
api
```
## Accessibility [#accessibility]
Intensity is a color channel, so ActivityGrid always pairs it with a numeric summary of its total and peak. That summary
survives forced-colors and color-blind viewing. The interactive entry adds 2-D arrow-key navigation, announcing each
cell's value as you move. Give it an `anchor` and every cell is a dated day, so the readout names the date rather than a
position ("Mar 2: 12").
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Ordered values, one per cell. |
| `layout` | `"grid" \| "strip"` | 7-row calendar or single strip. |
| `shape` | `"square" \| "round" \| "dot"` | Cell mark: crisp square, soft corners, or padded dot. |
| `anchor` | `string \| Date` | First slot's calendar day (UTC) — pads the first column so weekday rows align. |
| `weekStart` | `0 \| 1` | Start of week for anchor alignment (0 Sunday, 1 Monday). |
| `cell` | `number` | Cell edge length in viewBox units. |
| `steps` | `number` | Intensity steps including the zero track (default 5, GitHub-like). Same knob, same name, as CalendarStrip, CoverageStrip, GardenGrid, HeatCell, HeatStrip and SpiralYear. |
| `domain` | `[number, number]` | Explicit range for level bucketing. |
| `title` | `string` | Accessible name; joins the auto summary. |
| `summary` | `string \| false` | Override or disable the auto summary. |
| `gap` | `number` | Gap between cells in viewBox units (default 2). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# BalanceBeam (/docs/charts/balance-beam)
BalanceBeam takes two values and tilts a beam toward the heavier one, so which side wins reads instantly. Inflow is 620,
outflow is 480, and the beam tilts toward inflow. The angle grows with the imbalance, but it **saturates** at `maxTilt`
degrees: past that point a steeper beam would imply a precision the eye can't extract, so read the tilt as direction
plus rough magnitude. The two weights are area-true — their area, not their width, is proportional to value. For a
precise comparison, use `PairedBars` or `Delta`.
```tsx
```
## Install [#install]
```tsx
import { BalanceBeam } from "@microcharts/react/balance-beam";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { BalanceBeam } from "@microcharts/react/balance-beam/interactive";
```
## When to use it [#when-to-use-it]
Use it for a buy vs sell or in vs out read in a sentence, a pro vs con weight in a KPI card, or an A-vs-B pair where
direction is the point. For exact ratios use PairedBars or Delta, for more than two items MiniBar, and for trends a
time-series chart.
## Sizing [#sizing]
**round weights + values**
```tsx
```
**balanced reads level**
```tsx
```
## Variants [#variants]
`mode="ratio"` is the default, because most two-sided comparisons are share-of-whole questions. `mode="difference"`
scales the imbalance by a shared `domain`, so rows on the same scale tilt comparably. Square weights are the default
because circles under-read area at this size; `shape="round"` swaps them.
```tsx
`"
>
```
`format` also takes `Intl.NumberFormatOptions` — with a `locale`, both the announced values and the in-chart numerals
(`label="values"`) follow that locale's own grouping and decimal marks.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
Equal values level the beam and report **"balanced"** rather than picking an arbitrary heavier side. Two zeros are the
same case: the beam stays level and both weights shrink to zero area.
## Four homes [#four-homes]
**In a sentence**
```tsx
Cash flow this month{" "}
{" "}
— inflow outweighs outflow, beam tilts right.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
+140
net inflow
```
**In a tab header**
```tsx
Operating
```
## Accessibility [#accessibility]
The accessible name states both sides and the winner — **"Inflow 620 vs Outflow 480; Inflow heavier."** — or "…;
balanced." when they match. The two channels always agree: the tilt direction and the larger weight area point at the
same side. The interactive entry eases the beam to its new tilt, reveals a side's value on hover or ←/→, and announces
when the heavier side flips.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `[{label,value},{label,value}]` | Exactly two items. |
| `maxTilt` | `number` | Degrees at full saturation (default 12). |
| `shape` | `"square" \| "round"` | Weight shape (default square). |
| `mode` | `"ratio" \| "difference"` | ratio = share-of-whole; difference = absolute, scaled by domain. |
| `fontSize` | `number` | Type size of the weight numerals, in viewBox units. Defaults from `height`. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# BenchmarkStrip (/docs/charts/benchmark-strip)
BenchmarkStrip reads one value against a list of peer values: a focal dot on a common scale, placed against the peers'
own **empirical** quantile bands, so you can tell whether the value is ordinary for that cohort or out at the tail. The
bands come from the measured peers and never from a fitted distribution, so the strip never implies a normal curve the
data doesn't back up. There is no axis: the band is the reference frame, and the dot's position in it is the whole read.
```tsx
`"
>
```
## Install [#install]
```tsx
import { BenchmarkStrip } from "@microcharts/react/benchmark-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { BenchmarkStrip } from "@microcharts/react/benchmark-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for a value against its cohort, per-row peer comparison in tables, and SLA context. For a single trend use
Sparkline; for two groups head-to-head use ABStrips.
## Sizing [#sizing]
**small samples stay honest**
```tsx
// n < 8 falls back to min–max — tail quantiles would be fiction
```
**polarity colors the dot**
```tsx
// latency: lower is better → below the median reads positive
```
## Variants [#variants]
```tsx
// 42 peer latencies (ms) const peerLatencies = [ 180, 201, 237, 286, 341, 396, 443, 394, 412, 413, 398, 372, 340, 310,
205, 196, 205, 230, 271, 322, 378, 347, 391, 421, 434, 430, 412, 383, 268, 239, 221, 218, 231, 261, 306, 275, 331, 382,
422, 447, 455, 447, ];
`"
>
```
```tsx
`"
>
```
`format` also takes `Intl.NumberFormatOptions` — with a `locale`, both the right-gutter label and the accessible
summary's numbers follow that locale's own grouping and decimal marks: the value above reads "1.240" in German, not
"1,240".
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
```tsx
`"
>
```
Below 8 peers, tail quantiles (p5/p95) are statistically unreliable, so the outer band falls back to the observed
min–max automatically; `range` forces either band at any sample size. A value outside the plotted `domain` clamps to the
edge and draws a small directional wedge instead of disappearing.
## Four homes [#four-homes]
**In a sentence**
```tsx
Latency vs peers{" "}
{" "}
— slower than 78% of the cohort.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
312
ms · 78th pctile
```
**In a tab header**
```tsx
checkout
```
## Accessibility [#accessibility]
The accessible name states the value, its percentile, the peer count, and the middle-half interval — **"312 — 43rd
percentile of 42 peers (middle half 244.5–408.5)."** A flat cohort states it plainly instead: "52 — all 8 peers at 50."
The interactive entry roves the five quantile edges with ←/→, announcing each edge's name and value (e.g. "p75:
408.5.").
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Peer values. |
| `value` (required) | `number` | The focal reading. |
| `range` | `"p5p95" \| "minmax"` | Outer band; minmax for small samples. |
| `label` | `"value" \| "percentile" \| "none"` | What the right gutter states (default percentile). |
| `positive` | `"up" \| "down"` | Which side of the band is good (colors the focal dot). |
| `median` | `boolean` | Center tick (default true). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# BiasStrip (/docs/charts/bias-strip)
A new device tracks its reference closely and still reads about 2 units high. BiasStrip plots that offset: each dot is
one pair placed at its mean and its difference, the dashed line is perfect agreement, the accent line is the measured
bias, and the faint band is the limits of agreement. The y-axis is symmetric about zero, so a systematic offset reads as
the whole cloud sitting above or below the line. Dots render at 75% opacity so overplot reads as density, and pairs
beyond the limits are enlarged and re-inked so outliers read on shape, not color alone.
```tsx
{"Device and reference "}
{" agree, +2 bias."}
```
## Install [#install]
```tsx
import { BiasStrip } from "@microcharts/react/bias-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { BiasStrip } from "@microcharts/react/bias-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it to check whether a new instrument, method, or model agrees with a reference, and for instrument drift in a KPI
card. For unpaired samples use MicroScatter; for a single time series use Sparkline.
Two methods can correlate perfectly and still disagree by a constant offset. BiasStrip plots the difference, so that
offset shows up as distance from the zero line where a correlation plot would hide it along the diagonal.
## Sizing [#sizing]
**in a sentence**
```tsx
device and reference agree, +2 bias
```
**99% limits of agreement**
```tsx
```
## Variants [#variants]
The band is the classic ±1.96σ limits of agreement. `limits` widens or tightens it (2.58 for about 99%) without changing
what any mark means.
```tsx
`"
>
```
```tsx
`">
```
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
Empty data draws just the frame with "No data." as the summary. Under 5 pairs the limits aren't meaningful, so the band
and bias line drop out and only the dots and the zero reference remain: the chart makes no interval claim at that sample
size, and the mean difference is still stated in the summary. When the two methods agree perfectly the band collapses to
a hair around the zero line rather than vanishing, so the measured result stays visible. Pairs with a non-finite value
are dropped before the stats and the count. Past 40 pairs the dots are uniformly downsampled for display, while the bias
and limits still use every pair.
## Four homes [#four-homes]
**In a sentence**
```tsx
Device vs reference{" "}
{" "}
— +2 bias, 18 of 20 within limits.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
+2.0
```
**In a tab header**
```tsx
Sensor A
```
## Accessibility [#accessibility]
The accessible name is the bias plus the evidence-backed agreement share — **"Bias +2.21 across 20 pairs; 90% within the
limits of agreement."** — or the shorter **"Bias +2 across 4 pairs."** when there are too few pairs to draw limits.
Localizing carries the number format through: with `locale="de-DE"` the same chart reads **"Bias +2,21 across 20 pairs;
90% within the limits of agreement."** The interactive entry steps pairs ordered by mean and announces each pair's mean,
difference, and whether it clears the limits.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ a; b }[]` | Paired measurements. |
| `limits` | `number` | k in bias ± k·σ (default 1.96 ≈ 95% limits of agreement). |
| `label` | `"bias" \| "none"` | Seat-gated bias caption (default) or hidden. |
| `r` | `number` | Base dot radius, clamped [1, 3]. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# BreathingDot (/docs/charts/breathing-dot)
BreathingDot takes a single level between 0 and 1 and shows how strained a system is right now, as an ambient signal you
can leave running in a corner of the interface. A core dot colored by band, and a ring whose distance from the core is
the level: that is the static frame, and it renders with zero JavaScript. The interactive entry adds the pulse: slow and
small when calm, fast and large when strained, so the state is legible without reading a number. This is a low-precision
ambient read; for an exact figure use `Progress` or `Sparkline`.
```tsx
```
## Install [#install]
```tsx
import { BreathingDot } from "@microcharts/react/breathing-dot";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { BreathingDot } from "@microcharts/react/breathing-dot/interactive";
```
## Motion, and reduced motion [#motion-and-reduced-motion]
The pulse **is** the encoding: its rate and amplitude snap to three named bands (calm, elevated, strained). Three,
because ambient motion supports about three discriminable states, and more would be false precision. This is the
deliberate exception to the no-looping-animation rule, allowed because the loop parameter (the rate) is the datum. The
animation is gated twice. It pauses when the dot scrolls off-screen (a shared observer), and it never runs at all under
`prefers-reduced-motion`; reduced-motion readers get exactly the static frame, where the ring's distance from the core
already carries the level. The band is announced through a polite live region only when it changes, never on every
pulse.
## When to use it [#when-to-use-it]
Use it for an ambient "how strained is it right now" read, a live status dot in a header or KPI card, or per-node load
in a dense table. For an exact load figure use `Progress` or `Sparkline`; for a trend over time, `Sparkline`. The
boundary with `HeartbeatBlip` is the data shape: a continuous level is a BreathingDot, discrete arriving events are a
HeartbeatBlip.
## Sizing [#sizing]
One `size` prop, in viewBox units, sets the glyph box — it defaults to `16`, about the height of a line of body text.
There is no separate `width`/`height`: the dot is square, and the rendered box widens only by the gutter a `label`
reserves. `fontSize` follows `size` unless you set it.
## Variants [#variants]
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
An unknown value (`null` or `NaN`) turns the dot gray, drops its ring, and **never pulses**, so an unknown system never
renders as a calm one.
`format`/`locale` reach the `label="value"` percent numeral — always a whole percent, so a locale mostly changes digit
shapes here, not grouping or decimals. The band words in the accessible summary ("calm" / "elevated" / "strained") stay
in `strings` (`BreathingDotStrings`), not the number formatter.
## Four homes [#four-homes]
**In a sentence**
```tsx
System load right now{" "}
{" "}
— moderate strain, pulsing at 42%.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
42%
current strain
```
**In a tab header**
```tsx
api
```
## Accessibility [#accessibility]
The accessible name is the level and its band word — **"Load 20% — calm."** — or **"Load unknown."** when the value is
missing. The band color is never the only signal (ring offset and pulse rate double it), motion is fully gated on
`prefers-reduced-motion`, and the live region announces band changes only.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | Level 0–1 (clamped). null / NaN → unknown. |
| `thresholds` | `[number, number]` | calm / elevated / strained edges (default [0.5, 0.8]). |
| `label` | `"value" \| "none"` | Percent numeral beside the dot. |
| `size` | `number` | Dot box edge in viewBox units (default 16). |
| `fontSize` | `number` | Type size of the percent numeral, in viewBox units. Defaults from `size`. |
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](/docs/quickstart#the-shared-grammar).
# BubbleRow (/docs/charts/bubble-row)
BubbleRow sizes up a handful of magnitudes against each other: one circle per item, with **area** (not width)
proportional to value. That is `r ∝ √value`, with no exceptions, since a linear-radius map would overstate every
difference by roughly its square. Area is the weakest common channel, so this is the catalog's low-precision exemplar:
**for a precise comparison, use `MiniBar`.** The value numerals are on by default, because a bubble twice the diameter
is four times the area and the eye can't reliably invert that. The LOW precision rating and the MiniBar steer are
printed in the catalog, in `/catalog.json`, and in this page's header.
```tsx
```
## Install [#install]
```tsx
import { BubbleRow } from "@microcharts/react/bubble-row";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { BubbleRow } from "@microcharts/react/bubble-row/interactive";
```
## When to use it [#when-to-use-it]
Use it for a few magnitudes with physical presence in a sentence, a market-size or segment impression in a KPI card, or
an editorial callout that prints the number too. For precise comparison use `MiniBar`; for trends, Sparkline. Keep it
under about eight items.
## Sizing [#sizing]
**the honesty comparison — same data as MiniBar**
```tsx
// area: low precision
// use MiniBar for a precise read
```
**baseline align — weights on a shelf**
```tsx
```
## Variants [#variants]
There is no sorting prop. The row renders in your data's order, so sort the data yourself when you want a ranked row.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
Empty data draws just the frame with "No data." as the summary. A `null` value keeps its slot with a small presence ring
and drops its numeral: it is neither removed from the row nor drawn as a zero-radius circle.
## Four homes [#four-homes]
**In a sentence**
```tsx
Market size by region{" "}
{" "}
— EMEA leads at 1.24B.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
1.24B
market size
```
**In a tab header**
```tsx
EMEA
```
## Accessibility [#accessibility]
The accessible name names the extremes — **"4 items; largest EMEA at 1,240, smallest LATAM at 210."** The interactive
entry roves the bubbles with ←/→ (or hover), announcing each one's exact value — **"EMEA: 1,240."** — the number the
area itself can't carry.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label, value }[]` | A few non-negative magnitudes. |
| `align` | `"center" \| "baseline"` | center (specimen) or baseline (weights on a shelf). |
| `label` | `"value" \| "both" \| "none"` | value (default), both, or none. |
| `gap` | `number` | Gap between bubbles in viewBox units (default 2). |
| `fontSize` | `number` | Type size of the value numerals, in viewBox units. Defaults from `height`. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Bullet (/docs/charts/bullet)
Quota attainment is 72 against a target of 80. Bullet puts the measure, the target tick, and the qualitative bands (poor
/ okay / good) on one row, for progress, SLAs, budgets, and quotas. The bands sit lowest and graduate in shade by step,
because they are context and the measure bar has to read as the loudest mark on the row. The measure is a thin band
centered in the track (Few's proportion, not a full-height fill), so the bands stay legible on both sides of it.
```tsx
```
## Install [#install]
```tsx
import { Bullet } from "@microcharts/react/bullet";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Bullet } from "@microcharts/react/bullet/interactive";
```
## When to use it [#when-to-use-it]
Use it for progress to a goal, a value against a target, and KPIs with thresholds. For trends over time use
[Sparkline](/docs/charts/sparkline); it is not a distribution chart.
## Sizing [#sizing]
`width` and `height` are viewBox units that also set the rendered pixel box — a bullet reads best wide and short. Omit
them and drive the width from CSS to fill a table cell or card column; the viewBox keeps the aspect ratio.
**default**
```tsx
// data alone → an intrinsic 80×16 box
```
**fixed size**
```tsx
// a bullet reads best wide and short
```
**responsive**
```tsx
// fills a table cell or card column, aspect ratio preserved
```
## Variants [#variants]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
## Four homes [#four-homes]
**In a sentence**
```tsx
Q3 quota attainment sits at{" "}
{" "}
— inside the good band, short of target.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
72 GB
of an 80 GB volume
```
**In a tab header**
```tsx
North
```
## Accessibility [#accessibility]
Bullet announces its value against its target — **"72 of 80 target."**. No color is required to read whether the target
was met: the measure's length and the target tick encode it by position, and the qualitative bands sit behind them. The
target is a tick rather than a second bar: a distinct shape at a distinct position, so the comparison survives grayscale
print and never depends on two colors staying apart. The interactive entry reads the value and target on hover and
focus.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | The measured value. |
| `target` | `number` | Target tick to compare against. |
| `bands` | `number[]` | Ascending qualitative thresholds. |
| `domain` | `[number, number]` | Explicit [0, max]; auto-fit otherwise. |
| `label` | `"none" \| "value" \| "target" \| "both"` | Value/target readout in a right gutter (default none). |
| `title` | `string` | Accessible name; joins the auto summary. |
| `summary` | `string \| false` | Override or disable the auto summary. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# BumpStrip (/docs/charts/bump-strip)
BumpStrip plots one entrant's rank over a run of periods on an inverted ordinal scale, so #1 sits on top and climbing
draws upward. Ranks are not values: a Sparkline would treat the gap between #2 and #3 as a real distance. End labels
("#5" → "#1") anchor the ordinal read without an axis, and change dots mark only the periods where rank moved, so flat
runs stay quiet and the eye lands on the transitions.
```tsx
```
## Install [#install]
```tsx
import { BumpStrip } from "@microcharts/react/bump-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { BumpStrip } from "@microcharts/react/bump-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for leaderboard rows and category-rank trends in KPI cards. For continuous values use Sparkline, and keep it
under about 15 rank levels.
## Sizing [#sizing]
**leaderboard rows**
```tsx
{products.map((p) => (
))}
```
**gaps for unranked weeks**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
```tsx
`"
>
```
A single ranked period draws its two end labels with no visible line, because there is no trajectory yet. An all-`null`
series renders the frame with neither line nor labels, and the accessible name reports that directly. A flat run draws a
level line with zero change dots, because `dots="changes"` marks only the periods where rank moved.
## Four homes [#four-homes]
**In a sentence**
```tsx
Our blend's Coffee bestseller rank{" "}
{" "}
— from #5 to #1 in twelve weeks.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
#1
up from #5, 12 weeks ago
```
**In a tab header**
```tsx
Coffee
```
## Accessibility [#accessibility]
The accessible name is the full trajectory — **"From #8 to #1 over 12 weeks; best #1."** The interactive entry steps the
periods (**"Week 7 of 12: #2."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `(number \| null)[]` | 1-based integer ranks; null = unranked period (gap). |
| `maxRank` | `number` | Fix the band so small multiples share a rank scale. |
| `dots` | `"changes" \| "none"` | Mark the moments rank actually moved. |
| `label` | `"ends" \| "last" \| "none"` | "#5" → "#1" endpoint labels. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# BurnChart (/docs/charts/burn-chart)
Six days into an eleven-day sprint, 19 points are done against 20 planned. BurnChart draws the **plan** line (dashed,
full length to the deadline), the **actual** line to today, and a **dotted projection** whose slope is a plain linear
fit over the last `max(2, ⌈actual/3⌉)` actual points. History is solid and exact; the projection is dotted and muted,
and its method is stated rather than smoothed into an optimistic curve. The gap label states the signed schedule
landing.
```tsx
`"
>
```
## Install [#install]
```tsx
import { BurnChart } from "@microcharts/react/burn-chart";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { BurnChart } from "@microcharts/react/burn-chart/interactive";
```
## When to use it [#when-to-use-it]
Use it for a sprint burndown in a tab header, will-we-finish in a KPI card, and plan vs actual with a projected landing.
For a single progress number use Progress; for a plain series use Sparkline.
## Sizing [#sizing]
**plan vs actual only (retrospective)**
```tsx
```
**burn-up toward scope**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
With a `locale`, the accessible summary's numbers follow that locale's own grouping — "2.300" in German, not "2,300".
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`">
```
With no plan (`plan: []`), the projection still runs, since the fitted slope only needs `actual`, but there is no plan
to compare against, so the summary drops the "vs planned" clause. A stalled burn (`actual` flat) never reaches zero, so
`finishes` is `false` and the summary says so outright ("not finishing at the current pace") rather than extrapolating a
landing date. A single `actual` point draws no projection at all: the fit needs at least two.
## Four homes [#four-homes]
**In a sentence**
```tsx
Sprint 12 burndown{" "}
{" "}
— on track, 18 points left with 4 days to go.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
18
points remaining
```
**In a tab header**
```tsx
Sprint 12
```
## Accessibility [#accessibility]
The accessible name states progress against the plan and the projected landing — **"6 of 11 days in: 19 points done vs
20 planned — projected to finish 2 days late."**. The interactive entry steps the days; history announces actual vs
plan, and the dotted region announces the projection.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ plan: number[]; actual: number[] }` | Remaining work per period (mode='down') or completed (mode='up'). |
| `mode` | `"down" \| "up"` | Burn-down (remaining → 0, default) or burn-up (done → scope). |
| `projection` | `boolean` | The dotted extrapolation to the deadline (default true). |
| `work` | `string` | Work-unit noun for the summary and readout. Defaults to `strings.burnWork` ('points' in EN), so a localized bundle replaces it. |
| `unit` | `string` | Period noun for the summary and gap label (default 'day'). |
| `label` | `"gap" \| "none"` | Signed schedule landing vs the deadline in a right gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# CalendarStrip (/docs/charts/calendar-strip)
Each cell is one dated day, sitting in its real week column: four weeks by default, starting Monday, so one glance
covers a month of weekday rhythm without scrolling history. Weekday rhythm is the read.
```tsx
```
## Install [#install]
```tsx
import { CalendarStrip } from "@microcharts/react/calendar-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { CalendarStrip } from "@microcharts/react/calendar-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for habit or deploy cadence in KPI cards and week-aligned recent activity. For long ordinal histories use
ActivityGrid; for exact per-day values use MiniBar.
## Sizing [#sizing]
**habit rows**
```tsx
{habits.map((h) => (
))}
```
**dot cells for dense cards**
```tsx
```
## Variants [#variants]
```tsx
`">
```
```tsx
`">
```
All date math is UTC, so the same input renders identically in any host timezone and SSR output never depends on where
the server runs. `end` defaults to today; pin it wherever determinism matters.
`cell`/`gap` bump the edge length and spacing directly — grid-sibling parity with ActivityGrid and GardenGrid.
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
Empty is not zero here. A day with no record renders as a faint outline; a day with value 0 renders as a filled track
cell, so a gap in the feed never reads as a quiet day. Future days are blank, never extrapolated.
## Four homes [#four-homes]
**In a sentence**
```tsx
api shipped on 11 of the last 24 tracked days{" "}
{" "}
— quiet on three.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
11
of 24 tracked days
```
**In a tab header**
```tsx
production
```
## Accessibility [#accessibility]
The accessible name counts real days — **"Active 11 of 24 days over 4 weeks."** The interactive entry walks the grid in
2-D and announces real calendar days (**"Thursday, June 11: 0."**, **"Tuesday, June 23: no data."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ date; value }[]` | Date-keyed values; duplicates sum with a dev warning. |
| `weeks` | `number` | Window length in whole weeks ending at `end` (default 4). |
| `end` | `string \| Date` | Last day of the window (defaults to today UTC — pin it for SSR determinism). |
| `weekStart` | `0 \| 1` | Locale start-of-week (default Monday). |
| `steps` | `number` | Intensity steps including the zero track (default 5). |
| `shape` | `"square" \| "round" \| "dot"` | Shared cell vocabulary. |
| `cell` | `number` | Cell edge length in viewBox units (default 7). |
| `gap` | `number` | Gap between cells (default 1). |
| `dateFormat` | `Intl.DateTimeFormatOptions \| (d: Date) => string` | (interactive) Announced day label (defaults to weekday + month + day, UTC). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# CalibrationStrip (/docs/charts/calibration-strip)
CalibrationStrip draws one dot per bin at its predicted probability and its observed frequency, read against the
identity diagonal, with a support lane underneath. Bins with too little data render open and faded, so a
confident-looking dot is never backed by three samples. The support lane is always on and the count can't be disabled: a
reliability read without support disclosure is the failure this chart exists to prevent. There is deliberately no
single-number calibration score (ECE) rendered or announced, because one number would hide the per-bin structure.
```tsx
```
## Install [#install]
```tsx
import { CalibrationStrip } from "@microcharts/react/calibration-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { CalibrationStrip } from "@microcharts/react/calibration-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for classifier reliability and trust, and for probability-forecast auditing. For a single accuracy number use
Delta; for where the errors go use [ConfusionGrid](/docs/charts/confusion-grid).
## Sizing [#sizing]
**eval-table cell**
```tsx
```
**deviation bars**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
Nothing visible changes with `locale` — this chart draws no numerals. It localizes the announced numbers: with
`locale="de-DE"` the accessible name above reads "3 bins; largest gap at 0,7 predicted (observed 0,52); 0 low-support
bins." — the locale's own decimal mark, not "0.7".
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
`minSupport` defaults to `max(10, 2% of total)`. Bins below it render open and faded, and the summary counts them, so a
strip where every bin is sparse reads as one with no reliable bins rather than as a calibrated model.
## Four homes [#four-homes]
**In a sentence**
```tsx
Model calibration{" "}
{" "}
— well-calibrated above 0.6 predicted probability.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
0.92
ECE score
```
**In a tab header**
```tsx
v2.1
```
## Accessibility [#accessibility]
The accessible name reports the worst miscalibration and the support gaps — **"5 bins; largest gap at 0.7 predicted
(observed 0.52); 1 low-support bin."** The interactive entry roves the bins with ←/→, announcing each bin's predicted
and observed probability and its sample support.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `RawPair[] \| BinnedRow[]` | Raw pairs or pre-binned reliability rows. |
| `bins` | `number` | Uniform bin count for raw input. |
| `minSupport` | `number` | Below this a bin renders low-confidence. |
| `mode` | `"dots" \| "bars"` | Bars draw signed deviation columns. |
| `color` | `string` | Accent stroke/fill override. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# ChangePoint (/docs/charts/change-point)
An error rate sat at 30 for two weeks, then sat at 48. ChangePoint shades the two regimes, draws a mean hairline through
each, and marks the break between them, so a reader gets "errors stepped up here" rather than "errors are high". The
detector is a **documented heuristic, not statistics**: a two-segment mean-shift found by binary segmentation, accepted
only when the split both cuts the variance enough and clears an effect-size threshold, so a constant series shows no
break. Pass your own break indices and detection switches off — the chart becomes pure annotation.
```tsx
`"
>
```
## Install [#install]
```tsx
import { ChangePoint } from "@microcharts/react/change-point";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { ChangePoint } from "@microcharts/react/change-point/interactive";
```
## When to use it [#when-to-use-it]
Use it for context around an anomaly, an error rate / latency / cost that stepped to a new level, or annotating a known
deploy or incident with explicit `breaks`. Skip it for a gradual trend (Sparkline) or a plain time series with no regime
question.
## Sizing [#sizing]
**annotate a known deploy (explicit break)**
```tsx
```
**a gradual ramp has no level shift**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
This is a mean-shift test, not a trend test. A steady ramp is still fitted as two regimes: the second variant above
reports a shift around point 10. Pass `breaks` explicitly when you already know the series is trending rather than
stepping.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`">
```
With a `locale`, the summary and delta label format their numbers in that locale's own grouping and decimal marks.
Auto-detection (`breaks="auto"`) only runs at 8 or more points. A six-point series with an obvious jump still renders as
one flat, unbroken regime unless you pass `breaks` explicitly, because a two-segment fit on that few points is mostly
noise. A single point has no line to draw and no break to find; `data={[]}` renders the frame with the "no data" summary
and no marks.
## Four homes [#four-homes]
**In a sentence**
```tsx
Error rate this week{" "}
{" "}
— step-up on day 14, regime break at 2.1%.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
2.1%
post-change regime
```
**In a tab header**
```tsx
checkout
```
## Accessibility [#accessibility]
The accessible name states the shift, the break, the regime means, and what followed: **"Level shifted up 60% around
point 14 (mean 30 → 48); stable since."** With no detected shift it reads "No clear level shift across N points." The
interactive entry steps the points with ←/→ (value + regime) and cycles the breaks with Tab, each announcing its mean
shift.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | A single series. |
| `breaks` | `"auto" \| number[]` | Explicit indices override the heuristic entirely — the production path. |
| `maxItems` | `number` | Max detected breaks (1–3). More regimes stop being glanceable. |
| `means` | `boolean` | Per-regime mean hairlines (default true). |
| `label` | `"delta" \| "none"` | Signed % across the most recent break, in a gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# CitySkyline (/docs/charts/city-skyline)
CitySkyline compares a handful of groups on two variables at once: how big each one is, and how activated it is.
**Height** is the primary, precise channel: zero-anchored bars, like a MiniBar. The **lit-window fraction** is a
secondary, low-precision channel you read as "mostly lit, half lit, or dark", not as a number. Omit `lit` everywhere and
you get a plain bar row.
```tsx
`"
>
```
## Install [#install]
```tsx
import { CitySkyline } from "@microcharts/react/city-skyline";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { CitySkyline } from "@microcharts/react/city-skyline/interactive";
```
## When to use it [#when-to-use-it]
Use it for team or region size plus an activation read, an org KPI where two variables are the story, or a per-BU
comparison with utilization. For a single variable reach for `MiniBar`; skip it when you need a precise activation read,
or when you have more than about eight groups.
## Sizing [#sizing]
**omit lit for a plain bar row**
```tsx
({ label, value }))} />
```
**labelled, the two-variable read**
```tsx
```
## Variants [#variants]
Windows appear whenever `lit` is present. Heights stay zero-anchored bars either way, and nothing is encoded in building
width: width, roof, and ground are constants.
```tsx
({ label, value }))} />
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
All-equal values render as a flat roofline; no variation is added to make the row look busier. Lit windows are quantized
to the window count and filled bottom-up, so activation reads as a fill level. The secondary channel drops out before
the primary: a building too short for one window row renders as a solid tower, and its `lit` still shows in the
per-building readout on hover or keyboard focus.
## Four homes [#four-homes]
**In a sentence**
```tsx
Team sizes across eng{" "}
{" "}
— Platform is largest at 46, 70% activated.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
46
heads · 70% lit
```
**In a tab header**
```tsx
Platform
```
## Accessibility [#accessibility]
The accessible name gives the count and the tallest: **"3 groups; tallest A at 46."** The interactive entry roves the
buildings with ←/→ or hover and announces each one. On the team demo at the top of this page, Platform reads "Platform:
46; 70% lit."
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label, value, lit? }[]` | value = height; lit = 0–1 window fraction. |
| `labels` | `boolean` | Category labels under the buildings. |
| `ground` | `boolean` | The baseline hairline (default true). |
| `label` | `"none" \| "value"` | Numeral above each building. |
| `unit` | `string` | Category noun for the summary (default 'groups'). |
| `bw` | `number` | Building width in viewBox units (default 9). |
| `gap` | `number` | Gap between buildings in viewBox units (default 3). |
| `fontSize` | `number` | Type size of the value numerals, in viewBox units. Defaults from `height`. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# CohortTriangle (/docs/charts/cohort-triangle)
At month 1, the March vintage retains worst: 47%. Reading down a column compares every cohort at the **same maturity**,
which is the question this chart is for. CohortTriangle stacks cohorts as rows and ages as columns, shading each cell by
a discrete retention level. Newer vintages have been observed for fewer ages, which gives the block its **triangle**
shape.
```tsx
```
## Install [#install]
```tsx
import { CohortTriangle } from "@microcharts/react/cohort-triangle";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { CohortTriangle } from "@microcharts/react/cohort-triangle/interactive";
```
## When to use it [#when-to-use-it]
Use it for monthly or weekly cohorts side by side, spotting which vintage decays worst, or reading retention at equal
maturity in a KPI card. Color intensity is deliberately approximate, so pair it with a number when you need exact
per-cell values. For a single cohort, [RetentionCurve](/docs/charts/retention-curve) draws the decay directly.
## Sizing [#sizing]
CohortTriangle sizes from `cell` — the edge length of one square in viewBox units. Bump it to scale the whole grid; the
viewBox keeps the aspect ratio when CSS drives the width. Row labels seat automatically and drop out cleanly below
roughly an 8-unit cell.
**labelled grid**
```tsx
```
**highlight a vintage**
```tsx
// ring one cohort to anchor the equal-maturity comparison
```
**dense (no labels)**
```tsx
```
## Variants [#variants]
Rows are cohorts in input order and columns are age. Row labels are on by default; turn them off for a denser block, or
`highlight` one vintage to anchor the equal-maturity comparison. No worst-vintage flag is painted onto the grid — the
summary names the laggard in words instead.
```tsx
`"
>
```
The `unit` prop renames the age columns in the summary and announcements — `"month"`, `"week"`, `"day"`, whatever the
cohort period is.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`"
>
```
A single cohort skips the comparison and states its first reading. A `null` (or any non-finite) value renders as an
outlined **gap** slot, a "measured nothing here" cell, never a shaded 0%. `data` accepts either a 0–1 fraction or a
0–100 percent series, whichever the max value implies, so a raw percent export renders identically to its fraction form.
With a `locale`, every announced number follows that locale's own formatting.
## Four homes [#four-homes]
**In a sentence**
```tsx
January retention{" "}
{" "}
— the January vintage retains 37% at month 5.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
37%
M5 retention
```
**In a tab header**
```tsx
Jan
```
Best at KPI/card scale — cohort cells need room to resolve.
## Accessibility [#accessibility]
Intensity is quantized to five discrete levels rather than a continuous ramp, since a smooth gradient would imply a
precision a handful of pixels cannot deliver. It is still a color channel, so CohortTriangle always pairs it with a
numeric summary that compares at equal maturity: **"5 cohorts; at month 1, Mar retains worst (47%); newest May starts at
100%."**. Direction is stated in words, never by color alone, so it survives forced-colors and color-blind viewing. The
interactive entry adds 2-D arrow-key navigation, announcing each cell as *"Feb cohort, month 1: 58%"* as you move.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label, values }[]` | One row per cohort; values[i] = retention at age i (0–1 or 0–100, ragged). |
| `labels` | `boolean` | Cohort labels in a left gutter (default true; drops at tiny cell sizes). |
| `highlight` | `string` | Ring the cohort with this label — the comparison focus. |
| `unit` | `string` | Age-column noun for the summary (default "period"). |
| `cell` | `number` | Cell edge length in viewBox units. |
| `title` | `string` | Accessible name; joins the auto summary. |
| `summary` | `string \| false` | Override or disable the auto summary. |
| `gap` | `number` | Gap between cells in viewBox units (default 2). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# CometTrail (/docs/charts/comet-trail)
CometTrail plots the current value as a bright head dot, with recent points trailing behind it and fading with age. In
the interactive entry the head eases to each new value and the old head steps down into the trail, so a live stream
draws a comet and a stall goes still. Opacity encodes **age only**; the y position carries the value. The trail is
recency context, not data you have to decode.
```tsx
```
## Install [#install]
```tsx
import { CometTrail } from "@microcharts/react/comet-trail";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { CometTrail } from "@microcharts/react/comet-trail/interactive";
```
## Motion, and reduced motion [#motion-and-reduced-motion]
Motion happens **only on a data change**; there is no idle loop. When a new value arrives the head eases (\~200 ms, the
library's canonical strong ease-out) from its old position to the new one, and the previous head steps down into the
trail. A continuous stream produces the comet; a stalled stream goes still, which is itself the signal. The head never
simulates phantom positions between updates: it eases to each real value. Under `prefers-reduced-motion` the head
repositions instantly, since the static encoding is already complete. The same holds off-screen — one shared viewport
check means the ease only runs while the chart is in view.
## When to use it [#when-to-use-it]
Use it for a live price or metric with a little recency context, a realtime KPI that should show momentum, or per-stream
"where is it now" in a table. For the full history or an exact multi-point comparison reach for `Sparkline` (or
`DotPlot`); for discrete events, `HeartbeatBlip`.
## Sizing [#sizing]
`width` and `height` are viewBox units that also set the rendered pixel box — they default to `60 × 16`, sized to sit in
a table cell or beside a line of text. Omit them and drive the width from CSS to fill a column; the viewBox keeps the
aspect ratio. `trail` controls how many recent points the tail carries, which is a density decision rather than a size
one. It is capped at 20: past that the trail stops being a trail and becomes a sparkline, which is the better tool for
the full history.
## Variants [#variants]
`label="last"` is on by default, so the head always prints its number. Changing `trail` gives you more or less recency
context without changing the head read, because opacity encodes age and nothing else.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`"
>
```
Empty data draws just the frame, with "No data." as the summary. A single point has no trail to fade: the head alone,
and "Now 52." A `trail` larger than the data never backfills or pads. It shows every point there is.
## Four homes [#four-homes]
**In a sentence**
```tsx
p95 latency is climbing{" "}
{" "}
— now at 87.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
87
```
**In a tab header**
```tsx
checkout
```
## Accessibility [#accessibility]
The accessible name is the now-value and the recent trend: **"Now 62, rising over the last 3 updates."** With a single
point it is the value alone, as in the edge case above: **"Now 52."** Arrow keys step back through the trail ("3 updates
ago: 78.") and return toward now. Motion is gated on `prefers-reduced-motion`, and no reading depends on decoding the
fade.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | The rolling window, oldest → newest (last = now). |
| `trail` | `number` | Points kept visible (default 12, cap 20). |
| `label` | `"last" \| "none"` | Numeral after the head (default last). |
| `fontSize` | `number` | Type size of the numeral after the head, in viewBox units. Defaults from `height`. |
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](/docs/quickstart#the-shared-grammar).
# ConfusionGrid (/docs/charts/confusion-grid)
A classifier is 73% accurate; ConfusionGrid shows where the other 27% went, which is what an accuracy number hides. Rows
are the actual class, columns the predicted class, and each cell's ink is the row-normalized share: of the actual X,
where did the predictions go? The diagonal (agreement) is accented by an inset stroke rather than by color, and the ink
ramp is the same for agreement and error cells, so good and bad are positions on the grid. That is what keeps it
readable under forced colors. Put the one-line key — rows actual, columns predicted — next to every example.
```tsx
`"
>
```
## Install [#install]
```tsx
import { ConfusionGrid } from "@microcharts/react/confusion-grid";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { ConfusionGrid } from "@microcharts/react/confusion-grid/interactive";
```
## When to use it [#when-to-use-it]
Use it for classifier evaluation and any paired-classification agreement: triage vs outcome, plan vs actual, rater A vs
rater B. For a single accuracy number reach for Delta; for more than four classes, a full-size heatmap.
## Sizing [#sizing]
**KPI card**
```tsx
```
**worst-confusion accent**
```tsx
```
## Variants [#variants]
`accent="errors"` moves the accent from the diagonal to the worst confusion cell. `label="accuracy"` prints the accuracy
number; it is off by default and never rendered without the grid, so the number does not leave its context.
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
4 clamps to the first four classes (dev-warns)"
code="` `"
>
```
A fifth class doesn't grow the grid. Legibility caps at 4×4, so a `k` of 5 or more clamps to the first four labels and
rows/columns, with a dev warning steering to a full-size heatmap. The warning is dev-only, but the render truncates in
production too; pass pre-filtered `labels`/`counts` if you need a specific four.
```tsx
`"
>
```
## Four homes [#four-homes]
**In a sentence**
```tsx
Classifier accuracy{" "}
{" "}
— 87% cats correct, 12% dogs misclassified.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
87%
```
**In a tab header**
```tsx
cats
```
Best at KPI/card scale — the matrix is hard to read below ~36px.
## Accessibility [#accessibility]
The row view answers the question practitioners ask — "of the real cats, how many did we call dogs?" — so the summary
phrasing states the row normalization ("% of cats") and the denominator travels with every number. The grid at the top
of this page reads **"Accuracy 73%. Most confused: B predicted as C (43% of Bs)."** A perfect classifier reads
**"Accuracy 100%. No confusion."**, and a class with no samples is named rather than hidden: the `cat`/`dog` example
above reads **"Accuracy 80%. Most confused: cat predicted as dog (20% of cats). No dog samples."** The interactive entry
roves the cells with the arrow keys (Home and End jump to the two ends of the diagonal), announcing each cell's actual
and predicted class as a share of the actual class, with the tally it was computed from: **"Actual cat, predicted dog:
12% of cats (12)."** A row percentage cannot be inverted back to a count without the row total.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ labels, counts }` | k×k matrix; rows actual, columns predicted. |
| `normalize` | `"row" \| "none"` | Row = recall view (default). |
| `accent` | `"diagonal" \| "errors"` | Agreement or the worst confusion. |
| `label` | `"accuracy" \| "none"` | Overall accuracy in the gutter (opt-in). |
| `shape` | `"square" \| "round"` | Cell shape from the shared vocabulary (default 'square'). |
| `color` | `string` | Accent fill override. |
| `size` | `number` | Grid square edge in viewBox units. Defaults to a size that grows with the class count. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Constellation (/docs/charts/constellation)
Four incidents, the largest of them in September. Constellation places each event as a dot by time (x) and value (y),
with an optional magnitude driving its size by area (`r ∝ √m`), because area is how the eye reads magnitude. A hairline
connects the events in time order, so a handful of incidents shows its sequence. It is built for *rare* events, a dozen
or fewer; dense streams belong to `Seismogram` or `EventTimeline`.
```tsx
["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep"][x];
`"
>
```
## Install [#install]
```tsx
import { Constellation } from "@microcharts/react/constellation";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Constellation } from "@microcharts/react/constellation/interactive";
```
## When to use it [#when-to-use-it]
Use it for sparse incidents or outages on a timeline, milestones carrying a magnitude, or any rare events where the
sequence is the story. For dense event streams reach for `Seismogram` or `EventTimeline`; for a continuous trend,
`Sparkline`. Skip it when you need precise value comparison, since dot area is a low-precision channel.
## Sizing [#sizing]
**month labels in the summary**
```tsx
const monthFmt = (x: number) => ["Jan","Feb","Mar","Apr","May","Jun"][x];
```
**pure scatter (no chronology line)**
```tsx
```
## Variants [#variants]
`connect` is on by default, since with rare events the order and rhythm are usually the story. Turn it off for a plain
scatter, or set `label="max"` to print the largest event's value.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
Empty data draws no marks at all, just the sized box, with "No data." as the summary. A single event has no line to
draw: one dot, and the summary says "1 event at …" rather than forcing a range. When every point omits `y`, the vertical
position is **deterministic jitter that encodes nothing** — it spreads the dots so they do not stack. In that mode the
connector's slope carries no meaning (it still runs in time order), and neither the summary nor the interactive readout
mentions vertical position. The x position is never jittered, so simultaneous events sit at the same x.
## Four homes [#four-homes]
**In a sentence**
```tsx
Incidents this quarter{" "}
{" "}
— four events, largest severity in February.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
4
this quarter
```
**In a tab header**
```tsx
Prod
```
Best at KPI/card scale — event clouds need height to separate.
## Accessibility [#accessibility]
The accessible name states the count, the span, and the largest event: **"4 events between 0 and 8; largest at 8."** The
interactive entry lets you arrow through the events chronologically, each announced with its time, value, and magnitude
through a polite live region; a hover readout shows the same. Dot area is a low-precision channel, so magnitude is
always carried by a number as well, never by size alone.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ x: number; y?: number; m?: number }[]` | Events: x = time, y = value, m = magnitude (area-true size). |
| `connect` | `boolean` | The faint chronology line (default true). |
| `label` | `"max" \| "none"` | Numeral at the largest event. |
| `xFormat` | `(x: number) => string` | Formats time for the summary (e.g. a month name). |
| `xDomain` | `[number, number]` | Time (x) extent (default: data extent). |
| `rBase` | `number` | Base dot radius in viewBox units (default 1.6). |
| `fontSize` | `number` | Type size of the peak label, in viewBox units. Defaults from `height`. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# ControlStrip (/docs/charts/control-strip)
Thirty fill-weight readings off a production line, two of them outside the band. ControlStrip separates ordinary process
variation from a genuine excursion: it is a Shewhart individuals chart, and the control band is the process center ± 3σ̂,
where **σ̂ = mean moving range ÷ 1.128** — the individuals estimator, stated outright (sample SD is not used; it inflates
the limits under drift). In-control points are bare vertices. Only out-of-control points are marked, ringed in the
negative color, so the eye lands on the exception instead of scanning every dot.
```tsx
`"
>
```
## Install [#install]
```tsx
import { ControlStrip } from "@microcharts/react/control-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { ControlStrip } from "@microcharts/react/control-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for a production line or metric in a table cell, an SPC control chart in a KPI card, or flagging out-of-control
excursions. For a plain trend reach for Sparkline; for a strongly trending series, ChangePoint, since control limits
assume a stationary process.
## Sizing [#sizing]
**Western Electric run rules**
```tsx
```
**known baseline (golden period)**
```tsx
```
## Variants [#variants]
`rules="we"` adds the implemented Western Electric subset, enumerated so none of them fires silently: WE-1 (a point
beyond 3σ), WE-2 (two of three beyond 2σ), and WE-4 (eight on one side). `dots="all"` draws every point rather than only
the excursions, and `limits="percentile"` swaps the ±3σ̂ band for stated quantiles, for skewed processes.
```tsx
`"
>
```
```tsx
`"
>
```
The chart draws no in-chart numbers, so `format`/`locale` shape the accessible summary and the interactive
announcements. The strip above reads "All 12 points within control limits (center 2,5, limits 2,1–2,9)." with German
decimal commas.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
Below 10 points there are too few moving ranges to trust σ̂, so the band gets a dashed outline and the summary appends
"Limits provisional (n=6)". A constant series has MR̄ = 0 and therefore σ̂ = 0: the band collapses into the center
hairline rather than painting a fake zero-height zone.
## Four homes [#four-homes]
**In a sentence**
```tsx
Line 3 fill weight{" "}
{" "}
— in control except one excursion at t=18.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
74g
in control
```
**In a tab header**
```tsx
Line 1
```
## Accessibility [#accessibility]
The accessible name states the excursion count, the center, and the limits: **"2 of 30 points outside control limits
(center 74.17, limits 67.84–80.49)."** An in-control run reads "All N points within control limits …". The interactive
entry steps the points, and out-of-control points announce which limit they crossed.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Sequential measurements. |
| `limits` | `"sigma" \| "percentile"` | ±3σ̂ (default) or empirical p0.135/p99.865 for skewed processes. |
| `baseline` | `number` | Known process center from a reference period (else = mean). |
| `rules` | `"none" \| "we"` | Western Electric secondary run rules (WE-1/2/4 subset). |
| `dots` | `"out" \| "all" \| "none"` | Mark only out-of-control points (default), every point, or none. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# CoverageStrip (/docs/charts/coverage-strip)
A sensor was meant to report 18 times and reported 10. CoverageStrip fills the measured slots and outlines the missing
ones with a hairline, so you can see where nothing was measured before you trust the numbers. The distinction between
`null` (no measurement) and `0` (a measured zero) is the whole chart, and **shape** carries it, so it survives
forced-colors and print. Nothing is interpolated across a gap.
```tsx
```
## Install [#install]
```tsx
import { CoverageStrip } from "@microcharts/react/coverage-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { CoverageStrip } from "@microcharts/react/coverage-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for data-quality cells beside a metric, sensor-uptime rows, and trailing-gap detection. For magnitude over time
reach for HeatStrip; for exact values, Sparkline.
## Sizing [#sizing]
**trailing gap counts**
```tsx
// an array that simply stops is the worst gap of all
```
**zero ≠ missing**
```tsx
// a measured zero is filled; a gap is hollow
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
`mode="intensity"` shades each filled slot by its value; `shape="round"` swaps the slot shape. `locale` formats the
coverage percentage in the gutter and the accessible summary: the strip above reads "56 %" with German percent spacing
instead of "56%". `format` shapes the measured values the interactive entry announces.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
```tsx
`">
```
An `expected` beyond `data.length` pads the tail with hollow slots, so a feed that stops reporting counts as coverage
loss rather than dropping out of the denominator. A measured `0` is a filled cell like any other value; only `null` (no
record) is hollow. A missing slot stays visibly empty instead of being smoothed into its neighbors, and an empty series
renders the frame alone.
## Four homes [#four-homes]
**In a sentence**
```tsx
Sensor uptime this week{" "}
{" "}
— 86% coverage, two gaps on Tuesday.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
86%
sensor uptime
```
**In a tab header**
```tsx
North
```
## Accessibility [#accessibility]
The accessible name states how many slots were measured, the coverage percentage, and the longest gap: **"10 of 18 slots
measured (56 %); longest gap 4 slots."** An empty series states "No data." instead. The interactive entry roves slots
with ←/→, announcing each one as a measured value or "no measurement".
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `(number \| null)[]` | Time-ordered slots; null = no measurement, 0 = a measured zero. |
| `expected` | `number` | Slots the window should contain — lets trailing missingness count. |
| `mode` | `"binary" \| "intensity"` | Presence only (default), or shade measured cells by value. |
| `steps` | `number` | Intensity granularity (default 5). |
| `shape` | `"square" \| "round" \| "dot"` | Cell shape from the shared vocabulary (default 'square'). |
| `label` | `"percent" \| "none"` | State the coverage number in a right gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# CyclePlot (/docs/charts/cycle-plot)
Friday peaks at 61, Sunday dips to 38, and slot 2 has been climbing across three cycles. CyclePlot reshapes the series
into `period` slots (7 for weekdays, 12 for months). Each slot shows its own raw values across cycles as a muted
polyline in **time order**, plus a mean tick, and the accent spine connects the slot means. Seasonality is the spine and
drift is the within-slot lines: two questions of the same data, on two marks.
```tsx
`"
>
```
## Install [#install]
```tsx
import { CyclePlot } from "@microcharts/react/cycle-plot";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { CyclePlot } from "@microcharts/react/cycle-plot/interactive";
```
## When to use it [#when-to-use-it]
Use it for a KPI card that says "the week has a shape", for weekday traffic / hourly load / monthly sales seasonality,
or for spotting one slot that is itself drifting. For a plain time series reach for Sparkline; for one composition,
SegmentedBar. A `period` outside 4–12 warns in development: below that there is no cycle to see, above it the slots stop
being separable.
## Sizing [#sizing]
**median center (skewed slots)**
```tsx
```
**spine only (quiet form)**
```tsx
```
## Variants [#variants]
`center` picks the slot statistic: mean by default, `"median"` when a slot's distribution is skewed. The interactive
per-slot announcement names which one it used. `trend={false}` drops the spine and leaves the within-slot lines. Those
lines are never smoothed and never joined across a slot boundary — each polyline begins and ends inside its own column,
so a Monday trend cannot bleed into Tuesday.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`"
>
```
A slot with no finite values draws no mean tick and no within-slot line. The spine connects the non-empty slots only,
joining an empty slot's neighbours directly rather than dipping to a made-up center. A single cycle (one value per slot)
draws the spine and dots as usual, but no within-slot polylines, since a line needs at least two observations in a slot.
## Four homes [#four-homes]
**In a sentence**
```tsx
Weekly traffic shape{" "}
{" "}
— weekday peak Wednesday, weekend dip.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
Wed
peak day
```
**In a tab header**
```tsx
US
```
Best at KPI/card scale — weekly cycles need width for slot groups.
## Accessibility [#accessibility]
The accessible name states the peak and dip slots and any leading drift: **"Peaks slot 6 (61), dips slot 1 (38); slot 2
rising across 3 cycles."** The interactive entry steps the slots (mean, cycle count, drift) with ←/→, and steps the
individual observations within a slot with ↑/↓.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | A flat series, reshaped row-major into `period` slots. |
| `period` (required) | `number` | Slots per cycle (4–12) — e.g. 7 for weekdays. |
| `slots` | `string[]` | Slot names for summaries, e.g. weekday labels. |
| `center` | `"mean" \| "median"` | Center statistic — median for skewed slot distributions. |
| `trend` | `boolean` | Within-slot micro-trend line (default true); false = spine + ticks only. |
| `spine` | `boolean` | The slot-center spine (default true); false leaves within-slot drift only. |
| `cycleUnit` | `string` | Cycle noun for the summary, e.g. 'weeks' (default 'cycles'). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# DataDiff (/docs/charts/data-diff)
DataDiff compares two versions of a dataset and gives each key a diverging bar: **removed leftward, added rightward,
both always drawn** on one symmetric shared scale. Net is a mark you can turn on, never a replacement for the two bars.
A table that added 500 rows and dropped 480 nets to +20, the same as one that added 20 and dropped nothing, and those
are not the same event.
```tsx
```
## Install [#install]
```tsx
import { DataDiff } from "@microcharts/react/data-diff";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { DataDiff } from "@microcharts/react/data-diff/interactive";
```
## When to use it [#when-to-use-it]
Use it for a table cell per dataset version, a KPI card for a sync or import job, or any per-key added/removed audit
where churn matters. For a plain ranking reach for MiniBar; for parts of a single whole, SegmentedBar.
## Sizing [#sizing]
**key tags for standalone use**
```tsx
```
**net tick + totals footer**
```tsx
```
## Variants [#variants]
`labels` prints in-chart key tags, `net` adds a per-row tick at added − removed, and `label="totals"` prints a
`+added / −removed` footer. One symmetric scale spans every row, so a big change never shrinks to fit a small one; to
compare across charts, pass them a shared `domain`.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
An empty `data` array renders an empty chart and reports **"No data."** A non-empty diff where every key is 0/0 still
draws a hairline placeholder tick per row and reports **"No changes across 2 keys."** A key with no change keeps its
row, so the absence of a change never reads as the absence of the key.
## Four homes [#four-homes]
**In a sentence**
```tsx
Schema migration diff{" "}
{" "}
— 3 tables added, 1 column removed.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
+3
tables added
```
**In a tab header**
```tsx
users
```
## Accessibility [#accessibility]
The accessible name states the totals, the key count, and the single largest change: **"+468 added, −170 removed across
3 keys; largest change: users (+220)."** The interactive entry steps the rows and announces each key's added, removed,
and net change.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ key; added; removed }[]` | Per-key change counts — added and removed are non-negative magnitudes. |
| `labels` | `boolean` | In-chart key tags for standalone use (host tables carry keys by default). |
| `net` | `boolean` | A tick at added−removed per row — a summary mark, never the two bars. |
| `order` | `"data" \| "net" \| "magnitude"` | Default 'data' keeps input order (schema order is often meaningful). |
| `label` | `"totals" \| "none"` | 'totals' prints a +added / −removed footer. |
| `maxItems` | `number` | Row cap (default 12); rows beyond it are dropped with a dev warning. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Delta (/docs/charts/delta)
Delta prints a signed number with a direction glyph: `+12.4%` with an up triangle, `−8%` with a down one. It is
text-first, so it reads inline, next to a KPI, or inside a table cell. Direction is always doubled, a triangle *and* a
color, so it never relies on color alone: a red/green-only read fails for color-blind viewers and under `forced-colors`.
```tsx
```
## Install [#install]
```tsx
import { Delta } from "@microcharts/react/delta";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Delta } from "@microcharts/react/delta/interactive";
```
## When to use it [#when-to-use-it]
Use it for a KPI's change, a period-over-period percent, or inline metric movement. Skip it for showing a series or
comparing magnitudes across items.
## Sizing [#sizing]
Delta is text, not a fixed box — it takes the `font-size` of whatever wraps it and its glyph scales in `em`. Size it by
the surrounding type: inline, it inherits the sentence; beside a KPI figure, lift the font-size to match.
**inherits text size**
```tsx
// Delta is text — it takes the font-size of whatever wraps it
Revenue
```
**larger**
```tsx
// scale it up beside a KPI figure by lifting the font-size
```
## Variants [#variants]
`from` derives a percent change. `positive="down"` flips which color means "good" for metrics where down is good
(latency, churn, cost); it never changes which way the glyph points, so such a metric still shows a down-pointing
triangle in the "good" color. Percent is the default format, since most deltas are relative change rather than raw
magnitude. Pass `format` for currency, counts, or any other unit.
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
`format` also takes `Intl.NumberFormatOptions` — with a `locale`, the magnitude follows that locale's own grouping and
decimal marks (a German reader sees a comma decimal and a space before the `%`, not a period). The leading sign is the
library's own `+` / `−`, so it reads the same in every locale.
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
A zero delta shows the flat glyph and **"No change."**, never a `+0%`/`−0%` that implies a direction with nothing behind
it. `NaN` and `±Infinity` degrade the same way: the flat glyph, an em dash in place of a number, and **"No change."**
rather than a misleading `NaN%`.
## Four homes [#four-homes]
**In a sentence**
```tsx
API p95 latency held at 212ms this hour,{" "}
{" "}
from the hour before.
```
**In a table cell**
```tsx
{region.revenue}
```
**In a KPI card**
```tsx
$84.2k
```
**In a tab header**
```tsx
Growth
```
## Accessibility [#accessibility]
Delta renders accessible inline text — the glyph is decorative and the value carries the meaning. Color is a redundant
channel on top of the direction glyph and the sign, so the change survives forced-colors and color-blind viewing. The
interactive entry re-announces the figure through a polite live region when the value changes.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | The change, or current value when from is set. |
| `from` | `number` | Prior value; Delta shows the percent change. |
| `positive` | `"up" \| "down"` | Which direction is good (colors only). |
| `format` | `Intl.NumberFormatOptions \| fn` | Number formatting. |
| `locale` | `string \| string[]` | BCP 47 locale(s) for the formatted number. |
| `summary` | `string \| false` | Override or disable the auto summary. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# DepthWedge (/docs/charts/depth-wedge)
Demand outweighs supply 1.42× and the spread is 1. DepthWedge draws the shape behind those numbers: demand accumulates
leftward from the spread and supply rightward, so two filled wedges carry the book's posture in a glyph, with the spread
— the gap between the best bid and the best ask — printed above the mid line. The y-scale is linear, full stop. A silent
log axis would exaggerate near-spread depth and flatter thin books, and log-depth reading belongs to full-size tools.
```tsx
`"
>
```
## Install [#install]
```tsx
import { DepthWedge } from "@microcharts/react/depth-wedge";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { DepthWedge } from "@microcharts/react/depth-wedge/interactive";
```
## When to use it [#when-to-use-it]
Use it for order-book depth or liquidity and for supply vs demand posture. For a time series reach for Sparkline; for a
single ratio, Delta.
## Sizing [#sizing]
**pair cell**
```tsx
```
**normalized**
```tsx
```
## Variants [#variants]
`normalize` plots cumulative shares per side instead of absolute amounts. `levels` sets how far from the mid the wedges
reach, and that visible range is part of the claim: whenever the summary names a leading side it scopes the ratio with
"within the shown range".
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
Equal totals per side make the summary state the balance rather than name a lead. An empty side renders a single wedge
and the spread reads 0, since there is no gap to measure without both a best bid and a best ask.
## Four homes [#four-homes]
**In a sentence**
```tsx
Order book depth{" "}
{" "}
— bid-side liquidity thicker below mid.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
0.04
bid/ask imbalance
```
**In a tab header**
```tsx
BTC
```
Best at KPI/card scale — book depth needs width to show both sides.
## Accessibility [#accessibility]
The accessible name states the posture, scoped to the shown range: **"Demand outweighs supply 1.42× within the shown
range; spread 1."** — the hero demo's order book. The interactive entry walks the levels with ←/→, announcing the
cumulative depth on each side and its distance from the mid.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ demand, supply }` | Level/amount rows per side. |
| `levels` | `number` | ± level distance from mid to include. |
| `normalize` | `boolean` | Plot cumulative shares per side. |
| `label` | `"spread" \| "none"` | The gap is the headline number. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# DicePips (/docs/charts/dice-pips)
DicePips prints a small count as a die face: five pips for `5`, an empty face for `0`. The canonical 1–6 patterns are
subitizable, so you read them without counting. Zero means zero, not missing data. Above six there is no pattern to
subitize, so the face shows the exact numeral instead of inventing a seven-pip layout.
```tsx
```
## Install [#install]
```tsx
import { DicePips } from "@microcharts/react/dice-pips";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { DicePips } from "@microcharts/react/dice-pips/interactive";
```
## When to use it [#when-to-use-it]
Use it for a severity or rating 0–6 in a cell, a small count in a sentence, or an incident-severity badge. For counts
above six use TallyMarks, for magnitudes MiniBar, and for proportions Progress.
## Sizing [#sizing]
**pips-only for dense columns**
```tsx
```
**above 6 shows the exact numeral — no invented pattern**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
The face outline is on by default because it keeps a lone die legible on any surface. `face={false}` drops it for a
repeated table column, where the header already frames the column. The second demo is the numeral fallback at
`value={9}`.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
## Four homes [#four-homes]
**In a sentence**
```tsx
Incident severity{" "}
{" "}
— rated 4 of 6, elevated but not critical.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
4
of 6
```
**In a tab header**
```tsx
INC-142
```
## Accessibility [#accessibility]
The accessible name states the value against its range: **"4 out of 6."** Past six it drops the range and gives the
number alone (**"9."**). Zero reads "0 out of 6." and an invalid value reads "No data." The interactive entry pops the
pips into place on change — a short scale-up with a per-pip stagger, skipped under `prefers-reduced-motion` — and
announces the new face through a polite live region. The pips are one value, so there is no cursor to move.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | Integer 0–6 (rounded); above 6 shows a numeral. |
| `face` | `boolean` | Draw the die outline (default true). |
| `size` | `number` | Die square edge in viewBox units (default 16). |
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](/docs/quickstart#the-shared-grammar).
# DotPlot (/docs/charts/dot-plot)
DotPlot places each named value as a dot on one shared scale, which is the minimum ink per comparison. Reach for it over
bars when the scale doesn't start at zero: position stays readable where a truncated bar length would overstate the
difference. Without `stem` the domain fits the data and position is the read; with `stem` the domain is forced through
zero and the read becomes magnitude.
```tsx
`"
>
```
## Install [#install]
```tsx
import { DotPlot } from "@microcharts/react/dot-plot";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { DotPlot } from "@microcharts/react/dot-plot/interactive";
```
## When to use it [#when-to-use-it]
Use it for KPI leaderboards, named comparisons in cards, and rows where a truncated bar would overstate the difference.
Avoid it above 7 rows; for a time series use Sparkline.
## Sizing [#sizing]
**KPI leaderboard**
```tsx
```
**magnitude read (stems)**
```tsx
// stems force a zero-anchored domain — position becomes magnitude
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`"
>
```
An empty series has nothing to compare, so no dots draw and the accessible name reads **"No data."** A single category
still renders normally, one dot and one label, with the summary's "category"/"categories" wording matching the count
(**"1 category. Highest Only 42, lowest Only 42."**). A `null` value draws no dot and no label for its row, but the row
still occupies its slot in the vertical rhythm, so the real values around it keep the spacing they'd have in a fully
populated set. It is also excluded from the category count in the summary.
Two dots that would land on the same spot on adjacent rows de-overlap by half a unit. Labels truncate by character count
and drop entirely once rows get dense. Both are arithmetic on the layout; nothing is measured from the rendered text.
## Four homes [#four-homes]
**In a sentence**
```tsx
This cycle's review scores{" "}
{" "}
spread from Kim's 41
to Ada's 96 — a 55-point range.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
96
Ada, of 5 reviewed
```
**In a tab header**
```tsx
Engineering
```
## Accessibility [#accessibility]
The accessible name carries the count and the extremes: **"3 categories. Highest Ada 96, lowest Kim 41."** The
interactive entry roves rows and announces each one with its rank (**"Ada: 96 — 1st of 3."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; value }[]` | Named values. |
| `stem` | `boolean` | Hairline from zero — flips to a magnitude read (zero-anchored domain forced). |
| `highlight` | `number \| string` | Accent one category. |
| `label` | `"value" \| "none"` | Value text beside each dot (drops out under 8-unit rows). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# DualSparkline (/docs/charts/dual-sparkline)
Conversion is up 75% and the market it trades against is up 33%. DualSparkline puts both lines in one word-sized frame
so the gap between them is the read. Exactly two series, ever: three overlapped lines at 16 px are unreadable, and
SparkGroup is the chart for that. The benchmark renders dashed, thinner, and neutral, so it is never distinguished by
color alone. Both series share one domain, so there are no dual axes and no per-series normalization.
```tsx
```
## Install [#install]
```tsx
import { DualSparkline } from "@microcharts/react/dual-sparkline";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { DualSparkline } from "@microcharts/react/dual-sparkline/interactive";
```
## When to use it [#when-to-use-it]
Use it for metric-vs-benchmark in table cells and actual-vs-plan in KPI cards. For 3 or more series use SparkGroup, and
for series in different units use two charts, since this one never draws dual axes.
## Sizing [#sizing]
**channel rows vs market**
```tsx
{channels.map((c) => (
))}
```
**flip the primary — benchmark as the judged line**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
```tsx
`"
>
```
Each series carries its own gaps. A `null` in `data` breaks only the primary line at that index, a `null` in `compare`
breaks only the benchmark, and the nearest-x hover still reports both values (or "no data" for whichever side is
missing). A benchmark shorter than the primary series ends where its data ends; stretching it to span the frame would
fake a correlation. Endpoints that land on the same point dedupe to one dot. With a `locale`, the endpoint label and
both hover values follow that locale's grouping.
## Four homes [#four-homes]
**In a sentence**
```tsx
Checkout conversion is outrunning the market{" "}
{" "}
— up 75% vs the market's 33%.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
21%
plan 16% · +75% vs +33%
```
**In a tab header**
```tsx
Checkout
```
## Accessibility [#accessibility]
The accessible name reads both trends and both endpoints: **"Trending up 75% vs benchmark up 33%. Last 21 vs 16."**
Identical series read "Matching benchmark." The interactive entry announces pairs (**"Point 9 of 12: 17 vs 15."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `(number \| null)[]` | The series being judged. |
| `compare` (required) | `(number \| null)[]` | The benchmark — dashed, thinner, neutral. |
| `curve` | `"linear" \| "smooth" \| "step"` | Line shape (default 'linear'). |
| `band` | `[number, number]` | Normal-range band behind both (shared grammar). |
| `label` | `"last" \| "none"` | Endpoint value label for the primary series. |
| `dots` | `"auto" \| "none"` | Endpoint dots on both lines (default "auto"). |
| `seriesStrings` | `SeriesStrings` | i18n strings for the per-series trend clauses. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# DualWindowMeter (/docs/charts/dual-window-meter)
DualWindowMeter computes two rolling means from one raw series: a thin fast window that reacts instantly, and a thick
slow window that carries the sustained read. Both run against a target line, so a momentary spike stays separable from
sustained drift. The slow trace is the thicker of the two because drift away from target matters more than a spike, and
the window sizes are part of the reading, so `windows` is an explicit prop. It is the general form of any noisy metric
with a compliance target.
```tsx
```
## Install [#install]
```tsx
import { DualWindowMeter } from "@microcharts/react/dual-window-meter";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { DualWindowMeter } from "@microcharts/react/dual-window-meter/interactive";
```
## When to use it [#when-to-use-it]
Use it for loudness and LUFS metering, latency SLO compliance, and CPU-headroom compliance. For a single series use
Sparkline, and reach for something else when there is no target to compare against.
## Sizing [#sizing]
**latency SLO cell**
```tsx
```
**with corridor**
```tsx
```
## Variants [#variants]
```tsx
-22 + Math.sin(i / 3) * 4 + Math.sin(i / 11) * 2,
);
`"
>
```
```tsx
-22 + Math.sin(i / 3) * 4 + Math.sin(i / 11) * 2,
);
// both window sizes are part of the reading — state them, never hide them
`"
>
```
```tsx
-22 + Math.sin(i / 3) * 4 + Math.sin(i / 11) * 2 - (i > 40 ? 2 : 0),
);
`"
>
```
With a `locale`, the right-edge readings and the accessible summary follow that locale's decimal mark: the fast reading
above renders "-23,9" rather than "-23.9", and the accessible name reads "Slow window -25 vs target -23; fast -23,9."
## Edge cases [#edge-cases]
```tsx
`"
>
```
Both traces share one domain, and each begins only where its window has filled. With the default `windows={[3, 30]}` and
the 10 samples above, the fast trace starts at sample 3 and the slow trace never appears at all; its readout shows "—"
instead of a partial-window value.
## Four homes [#four-homes]
**In a sentence**
```tsx
Integrated loudness{" "}
{" "}
— −22 LUFS, within broadcast target.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
−22
LUFS integrated
```
**In a tab header**
```tsx
Track 1
```
## Accessibility [#accessibility]
The accessible name leads with the sustained read: **"Slow window -25 vs target -23; fast -23,9."** The interactive
entry roves the samples with ←/→, announcing the fast value, the slow value, and the target at each point.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Raw samples; two rolling means are computed. |
| `target` (required) | `number` | The compliance line — required. |
| `windows` | `[number, number]` | Fast/slow integration windows (samples). |
| `band` | `[number, number]` | A compliance corridor instead of one line. |
| `domain` | `[number, number]` | Fix the vertical scale instead of auto-fitting both traces. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Dumbbell (/docs/charts/dumbbell)
Dumbbell draws each row as a hollow dot at `from`, a filled dot at `to`, and a connector between them: a band that moved
62,000 → 84,000 reads without a legend. Direction is shape-coded, so it never relies on color alone, and the pair
renders the same whichever way the values run. With `positive` the connector takes the valence token by direction. Drop
`positive` for ranges such as min→max or a confidence span, which have no valence to color.
```tsx
```
## Install [#install]
```tsx
import { Dumbbell } from "@microcharts/react/dumbbell";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Dumbbell } from "@microcharts/react/dumbbell/interactive";
```
## When to use it [#when-to-use-it]
Use it for salary bands, before/after per table row, and ranges. When you have many categories and the crossings matter,
use Slope; when the path between the two ends matters, use Sparkline.
## Sizing [#sizing]
**table cell**
```tsx
```
**a range, not a change**
```tsx
// no positive prop — a min→max range has no valence to color
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
When a row's `from` equals its `to`, the connector is dropped and one filled dot is drawn, rather than a hollow and a
filled dot stacked on the same coordinate. The summary says **"No change at 55."** for a single row.
## Four homes [#four-homes]
**In a sentence**
```tsx
Berlin's band moved{" "}
{" "}
from €48k to €68k — up 42%.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
€48k → €68k
Berlin, up 42%
```
**In a tab header**
```tsx
Offices
```
## Accessibility [#accessibility]
A single row reads **"From 40 to 60, up 50%."**, a degenerate pair reads **"No change at 55."**, and a multi-row chart
leads with the largest change (**"2 rows. Largest change Paris, up 17%."**). The interactive entry roves rows with ↑/↓
(←/→ do the same) and announces each row's own pair: **"From 52 to 61, up 17%."**
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label?; from; to }[]` | Start/end pairs. |
| `positive` | `"up" \| "down"` | Direction valence for CHANGES; drop it for ranges (no valence). |
| `label` | `"value" \| "none"` | From/to values outside the dots (drop when the span is tight). |
| `highlight` | `number \| string` | Accent one row. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# EnsembleGhosts (/docs/charts/ensemble-ghosts)
EnsembleGhosts draws a faint bundle of member paths with one emphasized representative, so you can see where the
simulated futures disagree in **shape** and not only at the endpoint. Two ensembles can share an average and a final
range while running completely different routes to get there, which is what a mean line hides. The emphasized path
defaults to the real member closest to the pointwise median, so the representative is a path the simulation produced;
`emphasis="median"` draws the synthetic pointwise median instead. Ghost selection is evenly spaced endpoint-rank
quantiles, so the same input renders identically every time.
```tsx
`"
>
```
## Install [#install]
```tsx
import { EnsembleGhosts } from "@microcharts/react/ensemble-ghosts";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { EnsembleGhosts } from "@microcharts/react/ensemble-ghosts/interactive";
```
## When to use it [#when-to-use-it]
Use it for a KPI card that shows the futures rather than the average, for Monte-Carlo and simulation output where paths
disagree in shape, and for showing that outcomes fan out. When you need interval precision, use ForecastCone; for a
single path, use Sparkline.
## Sizing [#sizing]
**endpoint dots (countable spread)**
```tsx
```
**synthetic median path**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
`endpoints` marks where each ghost lands. `label="end"` is the default and states the emphasized path's landing value in
a right gutter; `label="none"` drops it.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
With one member there is no bundle to draw, so the chart renders that single path as the representative and the summary
states its ending value rather than a spread. A member containing a non-finite value (`NaN`, `Infinity`) is excluded
from ghost selection and from the median entirely, rather than gapped mid-path, and a dev-only console warning flags it:
a partially invalid future usually means bad simulation output.
## Four homes [#four-homes]
**In a sentence**
```tsx
Simulated futures{" "}
{" "}
— 70% of paths finish above target.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
70%
paths above target
```
**In a tab header**
```tsx
Base
```
## Accessibility [#accessibility]
The accessible name states the endpoint spread and the typical path: **"5 simulated paths end between 28 and 61; typical
path ends near 50."** The static frame is not a hypothetical-outcome plot. The HOP loop, which flips through the members
one at a time on hover, lives only in the interactive entry; with reduced motion, ←/→ step the members discretely
instead, and each one is announced with its endpoint.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[][]` | Ensemble members — 2–50 simulated paths. |
| `ghosts` | `number` | Rendered member count (deterministic endpoint-rank selection). Default 8, cap 12. |
| `emphasis` | `"nearest-median" \| "median" \| number` | A real median-like member, the synthetic median, or a pinned member. |
| `endpoints` | `boolean` | Ghost endpoint dots — makes the final-value spread countable. |
| `label` | `"end" \| "none"` | Emphasised path endpoint in a right gutter (default end). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# ErrorBudget (/docs/charts/error-budget)
A checkout SLO has 62% of its error budget left on day 12 of 30. ErrorBudget plots that remaining line against the
**steady-burn diagonal**, the pace that exactly spends the window, so "too fast" becomes a position: below the diagonal
means you are outrunning the budget. Faster burn-rate reference lines sit below it as faint policy context. The
1×/6×/14.4× multipliers are the Google SRE Workbook's multiwindow burn-rate alert **convention** rather than physics, so
`rates` is configurable and the lines never render data-colored.
```tsx
```
## Install [#install]
```tsx
import { ErrorBudget } from "@microcharts/react/error-budget";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { ErrorBudget } from "@microcharts/react/error-budget/interactive";
```
## When to use it [#when-to-use-it]
Use it for an SLO error budget in a KPI card, a service list where each row is a budget, and spotting a fast burn before
it exhausts the window. For a plain uptime series use Sparkline; for a one-number budget use Progress.
## Sizing [#sizing]
**fast-burn exhausts the window**
```tsx
```
**diagonal only (quietest form)**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`">
```
`format` defaults to a percent formatter, and with `locale` it follows that locale's own conventions for the
remaining-budget label: "62 %" with a space before the sign under `de-DE`, rather than the English "62%".
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
With no data the chart renders an empty root and the accessible name says **"No data."** A single point still draws the
diagonal, the wedges, and the endpoint, though `currentRate` reads `0`, since it needs a prior step: it is the observed
burn slope over the last `max(2, ⌈n/6⌉)` steps divided by the steady slope. Once the remaining line reaches zero, an ✕
at that first zero-crossing replaces the endpoint dot and the summary reads **"Budget exhausted at day 7 of 20."**
instead of the usual remaining-and-rate sentence. Values are clamped to `0–1`, so nothing draws below the floor or above
a full budget.
## Four homes [#four-homes]
**In a sentence**
```tsx
Checkout SLO budget{" "}
{" "}
— 34% remaining, burn rate elevated.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
34%
remaining
```
**In a tab header**
```tsx
checkout
```
## Accessibility [#accessibility]
The accessible name states the budget, the elapsed window, and the burn rate: **"62% of error budget remains at day 12
of 30 — burning at 0.6× the steady rate."** An exhausted window reads "Budget exhausted at day N of M." The interactive
entry steps the days and announces each step's remaining budget and local burn rate.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Budget remaining (0–1) per elapsed step; index 0 is 1.0. |
| `window` | `number` | Total steps in the SLO window (default = data.length). |
| `rates` | `number[]` | Burn-rate reference multiples (default the SRE 1×/6×/14.4× convention). |
| `unit` | `string` | Period noun for the summary (default "day"). |
| `label` | `"remaining" \| "none"` | Current budget % in a right gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# EtaBar (/docs/charts/eta-bar)
EtaBar takes a job's `progress`, `elapsed` time, and observed `rate` and shows how long is left at that pace. Its axis
is time rather than fraction: the solid part is the elapsed share of the predicted total, and the muted remainder is how
long the rest will take at the current rate. When the rate drops, the remainder grows instead of marching to a fixed
finish. The ETA label is on by default; `label="percent"` shows the fraction instead.
```tsx
```
## Install [#install]
```tsx
import { EtaBar } from "@microcharts/react/eta-bar";
`${Math.round(t)} min`} title="Export" />
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { EtaBar } from "@microcharts/react/eta-bar/interactive";
`${Math.round(t)} min`}
/>
```
## When to use it [#when-to-use-it]
Use it for download and export progress, job-queue ETAs, and deploy timers. For fraction-only progress use Progress; for
unbounded counters use Delta.
## Sizing [#sizing]
**table cell**
```tsx
```
**stalled**
```tsx
```
## Motion, and reduced motion [#motion-and-reduced-motion]
The interactive entry eases the elapsed/remainder split (SVG `x`/`width`) to its new geometry whenever `progress`,
`elapsed`, or `rate` change. The split only ever moves to a value computed from data you passed in on that render; it
never interpolates toward a frame it hasn't seen. The transition lives in shared CSS (`.mc-eta-live rect`), so it
inherits the shared `:where(.mc-root *) { transition: none }` reduced-motion block: under
`prefers-reduced-motion: reduce` the bar snaps straight to each new split instead of easing. The live region
re-announces the forecast on a throttle (`announceEvery`, default 10 s) so a fast-updating value doesn't spam a screen
reader.
## Variants [#variants]
```tsx
`">
```
```tsx
`"
>
```
Without an `etaFormat`, the remaining-time figure falls back to `format`/`locale`: under `de-DE` that's a decimal comma
("11,6") rather than the English decimal point ("11.6"). Pass `etaFormat`, as the hero example does, to add a unit. It
always receives the raw number, so it can format the unit itself in whatever locale-aware way you need.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
```tsx
`">
```
An explicit `rate` of `0`, or an absent `rate` from which no positive average can be derived from `elapsed`, renders the
remainder as a diagonal-hatched texture instead of a solid bar. That texture means unknown, and the summary says
**"stalled"** rather than naming a duration. When the predicted remainder is far larger than the elapsed time, the done
segment would round to an unreadable sliver, so the geometry clamps it to a visible 10% and adds a chevron marking the
clamp as approximate. At `progress >= 1` the bar fills completely regardless of `rate`.
## Four homes [#four-homes]
**In a sentence**
```tsx
Export progress{" "}
{" "}
— 64% done, ~2 min remaining.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
64%
~2 min left
```
**In a tab header**
```tsx
report
```
## Accessibility [#accessibility]
The accessible name is the forecast: **"42% done; about 11.6 remaining at the current rate."** A stalled transfer reads
**"30% done; stalled."** and a finished one **"Done."** The interactive entry re-announces on a throttle as the forecast
changes.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `progress` (required) | `number` | Completed fraction 0–1. |
| `elapsed` (required) | `number` | Time spent, any unit. |
| `rate` | `number` | Progress per time unit — pass a recent-window rate. |
| `label` | `"eta" \| "percent" \| "none"` | The remaining-time read is the product. |
| `etaFormat` | `(t: number) => string` | Unit-bearing ETA label ("2 min") — the caller owns units. |
| `announceEvery` | `number` | (interactive) Minimum ms between live-region announcements as the ETA streams (default 10000). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# EventRaster (/docs/charts/event-raster)
EventRaster draws one tick per event and one lane per source, all on a shared time domain. A vertical band means those
sources fired together, a diagonal means one triggered the next, and a sparse row means silence. Lanes stay aligned to
that shared domain because the banding is the phenomenon the chart exists to show. One tick is always one event; the
single exception is `overflow="bin"`, and the summary discloses it.
```tsx
```
## Install [#install]
```tsx
import { EventRaster } from "@microcharts/react/event-raster";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { EventRaster } from "@microcharts/react/event-raster/interactive";
```
## When to use it [#when-to-use-it]
Use it for service events across sources, agent steps, and cron or sensor triggers. For a single lane use RugStrip; for
continuous rates use Sparkline.
## Sizing [#sizing]
**table cell**
```tsx
```
**emphasis**
```tsx
```
## Variants [#variants]
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
i * 0.3) }]}
overflow="bin"
/>`"
>
```
```tsx
`">
```
An empty lane keeps its row, because a source that stopped firing is part of the reading. When a lane is dense enough
that its ticks would alias into a solid smear, `overflow="bin"` (the default) switches that lane to per-bucket counts
drawn as opacity, and the summary names the lanes that were binned. `overflow="clip"` opts out and keeps one tick per
event at any density.
## Four homes [#four-homes]
**In a sentence**
```tsx
Service events this hour{" "}
{" "}
— checkout-api busiest, 9 events.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
9
checkout-api/hr
```
**In a tab header**
```tsx
checkout
```
Best at KPI/card scale — multi-lane rasters need height per series.
## Accessibility [#accessibility]
The accessible name summarizes the field: **"3 lanes, 22 events; busiest api (11)."** The interactive entry roves lanes
with ↑/↓ and events with ←/→, announcing each event's lane, time, and position within the lane.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label, events }[]` | One lane per source. |
| `emphasis` | `string` | Accents one lane — the sync read. |
| `labels` | `boolean` | Left-gutter lane names (on ≤ 8 lanes). |
| `overflow` | `"bin" \| "clip"` | Aliasing lanes bin to counts (disclosed). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# EventTimeline (/docs/charts/event-timeline)
An API had a freeze window overnight, nine healthy hours, an incident at 11:00, and a release at 20:00. EventTimeline
puts all of it on one row: diamonds mark instants, rects mark durations. The type distinction is a shape, so it survives
12 px where color coding wouldn't. Duration is length on a linear time axis, never log or compressed time.
```tsx
`"
>
```
## Install [#install]
```tsx
import { EventTimeline } from "@microcharts/react/event-timeline";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { EventTimeline } from "@microcharts/react/event-timeline/interactive";
```
## When to use it [#when-to-use-it]
Use it for per-service uptime rows, and for on-call shifts and release windows in cards. Avoid it past roughly 12 items;
for aggregated durations use a MiniBar of the totals.
## Sizing [#sizing]
**uptime rows (shared window)**
```tsx
{services.map((svc) => (
))}
```
**with the current moment**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
A span that crosses the domain boundary is cut flat at the edge rather than rescaled to fit, so you see the part inside
the window and nothing more. An item that falls entirely outside the domain is excluded, with a dev-only console
warning. Spans that overlap render translucent in data order; that translucency is a legibility device rather than an
encoding, and the exact intervals are in the announcements.
## Four homes [#four-homes]
**In a sentence**
```tsx
API status today{" "}
{" "}
— 3 spans covering 63% of the window.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
63%
3 spans, 1 incident
```
**In a tab header**
```tsx
API
```
## Accessibility [#accessibility]
The accessible name is the coverage read: **"2 spans covering 46% of the window; 0 events."** Coverage merges intervals
first, so overlaps never double-count. The interactive entry cycles items chronologically: spans announce **"Freeze: Jun
3, 01:00 to Jun 3, 05:00 — 4h."**, instants **"Incident: Jun 3, 11:00."**
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ start; end?; label?; kind? }[]` | Spans (with end) and point events (without), ms epoch or Date. |
| `domain` | `[start, end]` | The window — fix it across rows for small multiples. |
| `now` | `number \| Date` | Current-moment tick; authored, never implicit. |
| `label` | `"none" \| "spans"` | Centered in-span labels with deterministic drop-out. |
| `dateFormat` | `Intl.DateTimeFormatOptions \| (d: Date) => string` | (interactive) Announced timestamp format for focused events (defaults to a locale date-time). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# FatDigits (/docs/charts/fat-digits)
FatDigits renders the exact number and maps its magnitude to font *weight*: five ordinal tiers across your `domain`, or
three with `tiers={3}`. The weight is a redundant second channel, so the large values in a dense column pop
preattentively before you read any of them. Five is the most weight steps that stay discriminable at text size. Weight
is never the primary read, since it is ordinal and coarse and the numeral is right there. Always pass a `domain`:
without one a lone number has no tier to sit in and renders at the middle weight.
```tsx
```
## Install [#install]
```tsx
import { FatDigits } from "@microcharts/react/fat-digits";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { FatDigits } from "@microcharts/react/fat-digits/interactive";
```
## When to use it [#when-to-use-it]
Use it for a dense numeric table column you scan for the big ones, a KPI number that should carry its own magnitude, or
an amount in a sentence. For trends use Sparkline, for proportions Progress, and for comparisons MiniBar.
## Sizing [#sizing]
**a scannable table column (the hero)**
```tsx
{rows.map((v) => )}
```
**digit mode weights each digit by its own magnitude**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
`encode="digit"` weights each digit by its own magnitude instead, a redundancy that helps you scan long ids and amounts.
The idea is adapted from FatFonts, which encodes magnitude as glyph ink area using a custom font; shipping a font would
add a dependency, so weight tiers on the inherited font carry the ordinal here.
`format` also takes `Intl.NumberFormatOptions`, and the numeral follows the locale's own grouping and decimal marks.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
A non-finite value renders nothing, neither numeral nor weight, and reports **"No data."** rather than guessing a tier.
On a font without many weights the browser snaps to the nearest available face, so fewer of the tiers stay visually
distinct.
## Four homes [#four-homes]
**In a sentence**
```tsx
Revenue column scan{" "}
{" "}
— Acme at 2.1M stands out.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
2.1M
```
**In a tab header**
```tsx
Acme
```
## Accessibility [#accessibility]
The accessible name is the exact value and its tier: **"1,204 — tier 3 of 5."** In digit mode it is the number alone,
since each digit carries its own weight. The interactive entry eases the weight to its new tier on variable fonts (it
snaps otherwise) with no layout shift, and announces the value and tier through a polite live region.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | The number (always the exact value). |
| `domain` | `readonly [number, number]` | Maps value to a weight tier — always pass one. |
| `encode` | `"value" \| "digit"` | value weights the whole numeral; digit weights each digit by its own magnitude. |
| `tiers` | `3 \| 5` | Weight steps (default 5). |
| `fontSize` | `number` | Numeral type size in viewBox units (default 14) — here the numeral is the mark, so this sizes the chart. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# FillWord (/docs/charts/fill-word)
FillWord makes the label the bar, so a named task shows its own progress: the word `uploading` renders with 62% of its
glyph ink in accent and the rest muted. A muted word is overlaid with an accent copy of itself, clipped to the value
fraction of the word's own inked extent, so 50% bisects the word. The fill is a percentage of the glyphs you see rather
than of a hidden wider track. Glyph ink is uneven, so fine reads are ±5%: the fill edge lands between letters, not on an
exact tick. Add `label="value"` for the percent alongside, or use Progress when precision is the point.
```tsx
```
## Install [#install]
```tsx
import { FillWord } from "@microcharts/react/fill-word";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { FillWord } from "@microcharts/react/fill-word/interactive";
```
## When to use it [#when-to-use-it]
Use it for a labeled progress read in a sentence or cell, a sync or upload status where the label names the task, or a
quota or TTL where the word is the metric. For precise percentages use Progress, for trends Sparkline, and for many
parallel bars MiniBar.
## Sizing [#sizing]
**drain mode for a remaining-time story**
```tsx
```
**show the exact percent alongside**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
Fill is the default mode, since a word filling as a step completes covers most uses. `mode="drain"` empties the ink from
the left as the value rises, which is the remaining-time read fill can't give you: TTLs, expiring sessions.
## Edge cases [#edge-cases]
```tsx
`"
>
```
At 0% the word is entirely the muted track; at 100% it is entirely accent ink. Both ends are limits of the same clip
rather than special cases in the code.
```tsx
`">
```
An empty `word` renders nothing and reports **"No data."**, since there is no label to be the bar.
## Four homes [#four-homes]
**In a sentence**
```tsx
Upload progress{" "}
{" "}
— 62% of the file transferred.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
62%
complete
```
**In a tab header**
```tsx
avatar
```
## Accessibility [#accessibility]
The accessible name gives the task and its state: **"storage: 40% complete."** In drain mode, where `value` is the
fraction already drained, `value={0.7}` reads **"expiring: 30% remaining."** An empty word reads "No data." The
interactive entry glides the ink edge with a reduced-motion-gated clip-path transition and announces changes through a
polite live region, throttled to at most once a second so a streaming value doesn't spam a screen reader.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `word` (required) | `string` | The text that is the chart. |
| `value` (required) | `number` | Fraction 0–1 (clamped). |
| `mode` | `"fill" \| "drain"` | fill grows the ink (complete); drain empties it (remaining / TTL). |
| `label` | `"none" \| "value"` | Append the percent numeral after the word. |
| `fontSize` | `number` | Word type size in viewBox units (default 12) — here the word is the mark, so this sizes the chart. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# FoldedDayBand (/docs/charts/folded-day-band)
FoldedDayBand folds many days (or weeks, or any cycle) onto a single period axis and draws the median with 25–75 and
5–95 percentile envelopes — the typical-period grammar clinical glucose profiles standardized on. The envelopes are
per-bin quantiles, never smoothed across bins, and the outer boundary fades so 5–95 doesn't read as a hard limit. Pass a
`today` overlay to see whether the current period sits inside the usual band.
```tsx
40 + 42 _ Math.max(0, 1 - Math.abs(h - 14) / 10); const observations = Array.from({ length: 14 },
(\_d, d) => Array.from({ length: 24 }, (\_h, h) => ({ t: d _ 24 + h, value: Math.round(curve(h) + Math.sin(d + h) \* 8),
})), ).flat(); const today = Array.from({ length: 24 }, (\_h, h) => ({ t: h, value: Math.round(curve(h) + 14), }));
`"
>
```
## Install [#install]
```tsx
import { FoldedDayBand } from "@microcharts/react/folded-day-band";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { FoldedDayBand } from "@microcharts/react/folded-day-band/interactive";
```
## When to use it [#when-to-use-it]
Use it for typical-day traffic or load profiles, and for on-call or energy capacity planning. For a raw time series use
Sparkline; a single period has nothing to fold.
## Sizing [#sizing]
**on-call cell**
```tsx
```
**now vs typical**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
`format` also takes `Intl.NumberFormatOptions` — with a `locale`, the accessible summary's fold position and peak value
follow that locale's own decimal mark ("14,0" in German, not "14.0"). The band and median line themselves don't change
shape; only the announced numbers are localized.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
({ t: i, value: 7 }))}
/>`"
>
```
A single observation reports a real accessible name (median and peak both equal that one value) but has no width to fold
across, so nothing visibly paints. Identical values across every bin still render as a flat line, distinct from an empty
`data` array, which renders nothing and announces "No data." When a bin holds too few observations the band collapses to
the median there rather than inventing a width.
## Four homes [#four-homes]
**In a sentence**
```tsx
Traffic vs typical day{" "}
{" "}
— afternoon peak normal; tonight runs hot.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
+14
above median at 2pm
```
**In a tab header**
```tsx
US-East
```
## Accessibility [#accessibility]
The accessible name reports the peak — **"Median peaks at 14 (82.5)."** The interactive entry roves the folded axis with
←/→, announcing the median and middle-half at each position.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ t, value }[]` | Raw observations across many periods. |
| `period` | `number` | Fold length (168 folds a week). |
| `today` | `{ t, value }[]` | The current period overlaid. |
| `percentiles` | `[number, number][]` | Percentile pairs, outermost last. |
| `bins` | `number` | Fold-axis resolution (default 24). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# ForecastCone (/docs/charts/forecast-cone)
Q4 revenue is at 38 and the target is 45: ForecastCone shows the spread of where it lands. History is a solid line, and
the forecast is a fan of prediction bands that **widen over the horizon** under a **dashed** median. Three of those
choices are fixed rather than style options. At most two bands, 50 and 80, because a 95% band reads as false tail
confidence at micro scale. The median is always dashed, so an estimate never renders as fact. And the cone must visibly
widen: bands that narrow or stay flat are flagged, never quietly inflated.
```tsx
```
## Install [#install]
```tsx
import { ForecastCone } from "@microcharts/react/forecast-cone";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { ForecastCone } from "@microcharts/react/forecast-cone/interactive";
```
## When to use it [#when-to-use-it]
Use it for a "will we hit Q4?" forecast in a KPI card, a projection with its uncertainty inside a sentence, and
band-vs-target landing reads. For a forecast with no uncertainty use Sparkline; for one estimate's spread, GradedBand.
## Sizing [#sizing]
**band vs target**
```tsx
```
**single band (tightest form)**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
`format` also takes `Intl.NumberFormatOptions` — with a `locale`, the in-chart landing label and the accessible
summary's median, interval, and target numbers all follow that locale's own grouping and currency placement (the label
above reads "4.200 €", not "€4,200").
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
With no history the cone still draws from the first forecast point, and the accessible summary drops its "from N today"
clause instead of naming a value that doesn't exist. A cone whose bands narrow instead of widen is an input error:
uncertainty that doesn't grow with the horizon misrepresents confidence decay. It renders exactly as supplied, never
auto-inflated, and logs a one-time dev warning so the mistake surfaces in development.
## Four homes [#four-homes]
**In a sentence**
```tsx
Q4 revenue forecast{" "}
{" "}
— median path clears target by week 3.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
112%
of target (median)
```
**In a tab header**
```tsx
Q4
```
## Accessibility [#accessibility]
The accessible name states the median, the horizon interval, and today's actual. With a `target` it adds whether the
band clears it: **"Median forecast 42 by week 11 (80% between 33 and 55), from 38 today. The 80% band straddles the 50
target."** The interactive entry is region-aware: history points announce a value, forecast points announce the median
and 80% interval.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Historical actuals. |
| `forecast` (required) | `{ mid: number[]; p80: [lo,hi][]; p50?: [lo,hi][] }` | Median + prediction bands (at most 2: 50/80). |
| `target` | `number` | The landing reference the cone must clear (adds a clearance clause). |
| `unit` | `string` | Period noun for the summary (default "week"). |
| `label` | `"landing" \| "none"` | Median endpoint value in a right gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Funnel (/docs/charts/funnel)
Funnel prints one stepped rectangle per stage — 12,400 visitors, 5,704 signups, 2,730 activated, 1,116 paid — each
anchored at zero, so the drop *between* neighbors is the read: the biggest one is where the pipeline leaks. It never
draws the classic tapered silhouette, which interpolates data that doesn't exist.
```tsx
```
## Install [#install]
```tsx
import { Funnel } from "@microcharts/react/funnel";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Funnel } from "@microcharts/react/funnel/interactive";
```
## When to use it [#when-to-use-it]
Use it for per-campaign funnels in table cells and conversion bridges in cards. For unordered categories use MiniBar,
and keep a funnel to 6 stages or fewer.
## Sizing [#sizing]
**table cell**
```tsx
```
**the leak**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
`mode="rate"` always normalizes to stage 1, never to the previous stage: per-stage rates hide compounding loss.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
A single stage renders as one full-height column with no connector slats, at an overall 100%. A stage larger than its
predecessor renders at its true size, as re-engagement funnels do, and the summary appends the inversion.
## Four homes [#four-homes]
**In a sentence**
```tsx
This week's signup funnel{" "}
{" "}
converted 12,400
visitors to 1,116 paid — a 9% overall rate.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
9%
12,400 → 1,116
```
**In a tab header**
```tsx
Organic
```
## Accessibility [#accessibility]
The accessible name states the span and the overall rate: **"4 stages, 12,400 to 1,116 — overall 9%."** It appends
**"Stage 3 exceeds stage 2."** when a pipeline inverts. The interactive entry roves stages, announcing each as
**"Activated: 2,730 — 22% of Visitors."**
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; value }[]` | Ordered stages. |
| `mode` | `"absolute" \| "rate"` | Rate = % of the FIRST stage (never the previous). |
| `connectors` | `boolean` | Retained-share slats between stages. |
| `label` | `"none" \| "percent" \| "value"` | Above each column (deterministic drop-out; default percent). |
| `highlight` | `number \| string` | Accent the leak stage. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# GardenGrid (/docs/charts/garden-grid)
GardenGrid shows the rhythm of activity over time the way ActivityGrid does, in a single ink. Dot **area** carries a
five-step ordinal, so the rhythm reads in grayscale and print where a color heatmap would collapse. The radius is
√-quantized so perceived area steps evenly; a linear radius map would exaggerate the highs quadratically. A zero cell is
a hairline ring, present but quiet, and a `null` cell is nothing at all, since print and grayscale otherwise lose the
zero-versus-missing distinction color grids get for free. The steps are ordinal rather than values: `ActivityGrid` is
the color twin when exact values matter.
```tsx
```
## Install [#install]
```tsx
import { GardenGrid } from "@microcharts/react/garden-grid";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { GardenGrid } from "@microcharts/react/garden-grid/interactive";
```
## When to use it [#when-to-use-it]
Use it for a contribution or activity rhythm you print or read in grayscale, a per-repo or per-team activity strip, or
any calendar-shaped intensity where color isn't available. For exact per-cell values use ActivityGrid with hover or
HeatStrip; for trends, Sparkline.
## Sizing [#sizing]
**strip mode for a table cell**
```tsx
const weeks = [12, 20, 8, 0, 15, 28, 34, 5, 0, 22, 18, 9];
```
**empty='blank' for sparse data**
```tsx
const weeks = [12,20,8,0,15,28,34,5,0,22,18,9,3,0,24,30,11,6,19,0,26];
```
## Variants [#variants]
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
## Four homes [#four-homes]
**In a sentence**
```tsx
Contributions this quarter{" "}
{" "}
— busy mid-month, quiet weeks 4 and 10.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
34
peak week
```
**In a tab header**
```tsx
microcharts
```
Best at KPI/card scale — activity grids need room for cell area.
## Accessibility [#accessibility]
The accessible name summarizes the rhythm: **"12 periods; peak 34, 10 active."** The interactive entry walks the grid in
2-D with the arrow keys (or hover), announcing each cell as its ordinal step — **"3 of 21: 8, step 2 of 5."** — because
dot area reads to a step rather than a number.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `(number \| null)[]` | Binned values; null = missing. |
| `rows` | `number` | Grid rows (default 7); 1 = strip. |
| `steps` | `3 \| 5` | Radius quantization steps (default 5). |
| `empty` | `"outline" \| "blank"` | How zero cells render (default outline). |
| `unit` | `string` | Noun for the summary count (default "periods"). |
| `cell` | `number` | Cell edge length in viewBox units (default 10). |
| `gap` | `number` | Gap between cells in viewBox units (default 2). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# GradeProfile (/docs/charts/grade-profile)
A stage climbs 67 m over 900, and the wall sits at 800. GradeProfile shows where. Each segment between two points is
filled from the baseline and colored by its grade, from gentle to brutal, with the elevation ridge riding on top, and
the steepest pitch is labeled so the hardest moment isn't buried.
**How to read it** — color is a *quantized* grade bin, never a smooth ramp: flats and every descent take the faint band,
then a mid tone, the negative color, and the darkest fill for the brutal pitches. The default `bins` of `[3, 6, 10]`
percent match how climbs are described, so a route reads as gentle, rolling, steep, or wall. Height is the elevation
ridge for shape; the encoded channel is the color, and the interactive readout gives the exact grade. Below 72 pixels
wide the four bins collapse to a flat-versus-climb read, because the finer classes stop being separable there.
```tsx
```
`d` and `elev` must share a unit (both metres, say) so that grade — rise ÷ run — is a true percent. Distance need only
be monotonic; the values themselves can be any scale.
## Install [#install]
```tsx
import { GradeProfile } from "@microcharts/react/grade-profile";
`${n} m`} title="Queen stage" />
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { GradeProfile } from "@microcharts/react/grade-profile/interactive";
```
## When to use it [#when-to-use-it]
Use it for route and climb profiles in cycling, running, and hiking, and to show *where* the hard pitches fall. For a
single elevation series use Sparkline; distance must be monotonic, so an unordered track won't work.
## Sizing [#sizing]
**stage cell**
```tsx
```
**call out the wall**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`"
>
```
`format` accepts `Intl.NumberFormatOptions` (with a `locale`) or a formatting function, and applies to **distance and
elevation** numbers in the summary and readout — not grade. Grades are always percent-encoded; the locale still decides
the decimal mark: the alpine profile above reads **"12.000, 1.250 gain; steepest 12,3% at 3.500."** in German.
## Edge cases [#edge-cases]
```tsx
\`\${n} m\`} title="Descent" />`"
>
```
```tsx
`"
>
```
A descent-only route has no climb to grade, so no pitch is emphasized and the summary reports **"600 m, no real
climb."** Descents always take the gentlest color rather than borrowing a climb's, because climbing difficulty is what
the chart grades. A non-finite elevation breaks the profile into a gap: the segments touching it drop out and the ridge
splits, so a missing reading never invents a grade. A flat route reports the "no real climb" sentence too, and a single
point has no segment to grade at all, so its accessible name falls back to **"No data."**
## Four homes [#four-homes]
**In a sentence**
```tsx
Queen stage elevation{" "}
{" "}
— 865 m gain over 800 km.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
865 m
total gain
```
**In a tab header**
```tsx
Stage 12
```
## Accessibility [#accessibility]
The accessible name gives the distance, the total climb, and where the hardest pitch falls: **"900, 67 gain; steepest
16% at 800."** None of it depends on color. The interactive entry announces each pitch as you rove it (`←`/`→`), with
the true grade and the cumulative climb.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ d: number; elev: number }[]` | Distance + elevation, monotonic in d, same unit so grade is a true percent. |
| `bins` | `[number, number, number]` | Ascending grade-% thresholds (always percent) that quantize the four difficulty bins. |
| `format` | `Intl.NumberFormatOptions \| (n) => string` | Formats distance and elevation in the summary and readout; grades always render as percent. |
| `label` | `"max" \| "none"` | Mark the steepest pitch, or render the profile alone. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# GradedBand (/docs/charts/graded-band)
GradedBand prints one number's uncertainty as nested central intervals, graded by opacity, with a median tick. Opacity
maps to probability level and nothing else. It is **never** a bar from zero: a bar with an error whisker induces
edge-literalism bias, where the bar's end reads as the answer and the whisker as noise, and a graded band has no
endpoint to over-read. Inner intervals are clipped inside their outer, so quantile rounding can never invert the
nesting.
```tsx
```
## Install [#install]
```tsx
import { GradedBand } from "@microcharts/react/graded-band";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { GradedBand } from "@microcharts/react/graded-band/interactive";
```
## When to use it [#when-to-use-it]
Use it for a forecast with its uncertainty, estimate-vs-actual in a KPI card, and posterior summaries. For countable
odds use QuantileDots; for a forecast over time, ForecastCone.
## Sizing [#sizing]
**estimate vs actual**
```tsx
// the dot is a distinct shape from the median tick
```
**soft edge = approximate**
```tsx
```
## Variants [#variants]
```tsx
21 + Math.round(9 * Math.sin(i) + 6 * Math.sin(i * 2.3)),
);
`"
>
```
```tsx
(21 + Math.round(9 * Math.sin(i) + 6 * Math.sin(i * 2.3))) / 10,
);
`"
>
```
With a `locale`, the median label and the accessible summary follow that locale's decimal mark — the label above renders
"2,2", not "2.2".
## Edge cases [#edge-cases]
```tsx
`"
>
```
The accessible name for that strip is **"Point value 42, no interval."** A degenerate sample is announced as a point
estimate rather than as certainty about a range.
```tsx
`"
>
```
## Four homes [#four-homes]
**In a sentence**
```tsx
Forecast estimate{" "}
{" "}
— 50% band spans 17–21, point at 19.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
19
median estimate
```
**In a tab header**
```tsx
Revenue
```
## Accessibility [#accessibility]
The accessible name states the median with the innermost and outermost intervals. For the forecast above that is
**"Median 22; 50% within 15–27, 95% within 8–36."** The interactive entry roves the band edges, announcing each
interval's coverage and bounds.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Sample / posterior draws; the component derives the intervals. |
| `levels` | `number[]` | 1–3 nested central intervals (default [50, 80, 95]). |
| `value` | `number` | Observed value overlaid as a dot. |
| `softEdge` | `boolean` | Fade past the outer band — 'this is approximate'. |
| `label` | `"median" \| "none"` | States the median in a right gutter (default none). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# HeartbeatBlip (/docs/charts/heartbeat-blip)
HeartbeatBlip draws one ECG-style spike per event in a rolling window, 60 seconds by default, because liveness questions
are about the last minute. In the interactive entry the trace sweeps left in real time and a new event blips in at the
right edge, so the rate you see **is** the event rate. Every spike is one real event, never a synthesized pulse on a
timer, and an empty window leaves a flat baseline: the down signal is carried by shape, not color.
Pass `now` from your data layer. The static entry never calls `Date.now()`, because a server render and the client
hydrate would disagree and mismatch.
```tsx
```
## Install [#install]
```tsx
import { HeartbeatBlip } from "@microcharts/react/heartbeat-blip";
// pass 'now' from your data layer — never Date.now() in a server render
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { HeartbeatBlip } from "@microcharts/react/heartbeat-blip/interactive";
```
## Motion, and reduced motion [#motion-and-reduced-motion]
The trace advances in real time, so old spikes drift left and new events enter at the right. This is the deliberate
idle-loop exception, allowed because the loop parameter (elapsed time) is the datum. It pauses off-screen through a
shared observer, and under `prefers-reduced-motion` it does not sweep at all: the static strip re-renders on each data
change instead, carrying the same information discretely. **A flat service never gets a fake pulse.** When the window
empties, the baseline goes flat and stays flat.
## When to use it [#when-to-use-it]
Use it for the liveness of a service or stream, a request rate in a header, or per-service liveness in a status table.
For exact event counts use `Seismogram` or `EventTimeline`, for a continuous level `BreathingDot`, and for long-term
trends `Sparkline`.
## Sizing [#sizing]
`width` and `height` are viewBox units that also set the rendered pixel box — they default to `60 × 16`, sized to sit in
a status table row or a header. Omit them and drive the width from CSS to fill a column; the viewBox keeps the aspect
ratio. `window` sets how much time the strip covers, which changes the density of the trace rather than its box.
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
`format`/`locale` only reach the `label="count"` numeral — the accessible summary's count is a plain integer (never run
through the formatter), and the in-chart spikes never carry text, so there's nothing else to localize.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`"
>
```
## Four homes [#four-homes]
**In a sentence**
```tsx
Request liveness{" "}
{" "}
— six events in the last minute.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
6
```
**In a tab header**
```tsx
api
```
## Accessibility [#accessibility]
The accessible name is the count, the window, and the time since the last event — **"3 events in the last minute; last
3s ago."** — or **"No events in the last minute."** when the trace is flat. Down and no-data are different states, and
the summary distinguishes them. The trace sweeps only when motion is allowed and the chart is on-screen. The live region
announces on data change, and there is no per-spike navigation, because the spikes are transient and the summary is the
record.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `events` (required) | `number[]` | Event timestamps (ms). |
| `window` | `number` | The visible recent window in ms (default 60000). |
| `now` | `number` | Explicit clock — defaults to the latest event (SSR-safe). |
| `label` | `"count" \| "none"` | Event-count numeral at the right. |
| `fontSize` | `number` | Type size of the count numeral, in viewBox units. Defaults from `height`. |
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](/docs/quickstart#the-shared-grammar).
# HeatCell (/docs/charts/heat-cell)
You lay out the grid — a matrix of tenants × hours, a custom calendar, a density chip in a sentence — and HeatCell is
the single cell inside it, showing how intense one value is against a known scale. Color is quantized into five discrete
steps, the most a reader reliably distinguishes at cell size and the same ramp ActivityGrid uses, so intensity means one
thing across the library. Continuous opacity would fake precision a 12-px cell can't deliver.
```tsx
{[12, 35, 58, 79, 96].map((v) => (
))}
```
## Install [#install]
```tsx
import { HeatCell } from "@microcharts/react/heat-cell";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { HeatCell } from "@microcharts/react/heat-cell/interactive";
```
## When to use it [#when-to-use-it]
Use it for table-cell matrices, custom grids, and intensity chips beside labels. For precise comparison use MiniBar or
DotPlot; for a time series, HeatStrip or ActivityGrid.
## The shared-domain rule [#the-shared-domain-rule]
A lone cell has no data to auto-scale from, so `domain` defaults to `[0, 1]` — pass your grid's real scale. **Every cell
in one grid must share one domain**: per-cell auto-scaling would make the brightest hour of a quiet tenant look like the
brightest hour of a loud one.
```tsx
// one domain, computed once, shared by every cell
const domain: [number, number] = [0, Math.max(...allValues)];
rows.map((r) => r.hours.map((v) => ));
```
## Sizing [#sizing]
**a shared-domain row**
```tsx
// every cell calibrates against ONE domain — never per-cell auto-scale
{[12, 40, 62, 88].map((v) => (
))}
```
**value chip**
```tsx
// wider cells can carry their number
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
Values outside the `domain` clamp to the end steps and are never silently rescaled, so one out-of-range cell can't
restretch a grid. A zero-width domain renders the single mid step and logs a dev warning.
## Four homes [#four-homes]
**In a sentence**
```tsx
shard-a load just crossed{" "}
{" "}
76% — level 4 of 5.
```
**In a table cell**
```tsx
// every cell in the grid shares ONE domain
shard-a
{[18, 42, 76, 55].map((v) => (
))}
```
**In a KPI card**
```tsx
76%
peak this cycle
{[18, 42, 76, 55].map((v) => (
))}
```
**In a tab header**
```tsx
shard-a
```
## Accessibility [#accessibility]
The accessible name carries the value *and* its calibration: the shape cells above read **"70 — level 4 of 5."** The
color scale is never the only channel. Non-finite input renders a designed empty track and says **"No data."** The
interactive entry reveals the same reading on hover and focus, with ActivityGrid announcement parity.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | The value to calibrate. |
| `domain` | `[number, number]` | Calibration scale — defaults to [0, 1]; every cell in a grid must share one. |
| `steps` | `number` | Discrete perceptual steps (default 5, shared with ActivityGrid). |
| `shape` | `"square" \| "round" \| "dot"` | Shared cell vocabulary. |
| `label` | `"value" \| "none"` | Centered number when the cell doubles as a chip. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# HeatStrip (/docs/charts/heat-strip)
HeatStrip prints one stepped intensity cell per point along a single timeline: it is the 1×N sibling of ActivityGrid,
with the same discrete step scale and the same cell vocabulary. A slot with no record renders a hairline outline,
visibly different from a low value, because empty is not zero.
Square cells with a density-adaptive gap keep boundaries legible where round shapes blur at 10 px. Every real value
renders at a visible opacity (0.25–1), and the faint track look is reserved for empty slots. The steps stay discrete,
the `domain` is shared rather than autoscaled per row, and downsampling takes the max per bucket rather than the mean.
```tsx
```
## Install [#install]
```tsx
import { HeatStrip } from "@microcharts/react/heat-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { HeatStrip } from "@microcharts/react/heat-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for per-tenant load rows and intensity ribbons in dense tables. For exact shape use Sparkline; for weekday
rhythm, ActivityGrid.
## Sizing [#sizing]
**shared-domain rows**
```tsx
// one domain per table — rows stay comparable
{tenants.map((t) => (
))}
```
**nulls hold their slot**
```tsx
// a missing record is visibly different from zero
```
## Variants [#variants]
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
A `null` holds its slot as a hairline outline instead of collapsing the strip, so positions stay aligned across stacked
rows.
## Four homes [#four-homes]
**In a sentence**
```tsx
API load over the last 20 minutes{" "}
{" "}
— peaked at 90%, closed at 35%.
```
**In a table cell**
```tsx
// one shared domain — rows stay comparable
{tenants.map((t) => (
{t.name}
))}
```
**In a KPI card**
```tsx
90%
peak, minute 8
```
**In a tab header**
```tsx
Acme
```
## Accessibility [#accessibility]
The summary reuses `describeSeries` verbatim: for the load strip above that is **"Trending up 383%. Range 12 to 88. Last
value 58."** A color ramp is never the only channel. The interactive entry roves cells with ActivityGrid-parity
announcements (**"Point 8 of 20: 90."**, empty slots as "no data").
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `(number \| null)[]` | Time-ordered values; null = no record (≠ zero). |
| `steps` | `number` | Shared step-scale granularity (default 5). |
| `shape` | `"square" \| "round" \| "dot"` | Shared cell vocabulary. |
| `domain` | `[number, number]` | Cross-row calibration — share one domain per table. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# HistogramStrip (/docs/charts/histogram-strip)
HistogramStrip sorts raw observations into uniform bins and draws the counts zero-anchored, never density-smoothed, so a
distribution's mode, spread, and skew read off one strip. The bin count is √n capped at 12: enough shape to see skew,
few enough bars to survive 60 px. Pre-aggregated counts are not supported; that's SparkBar's contract.
```tsx
```
## Install [#install]
```tsx
import { HistogramStrip } from "@microcharts/react/histogram-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { HistogramStrip } from "@microcharts/react/histogram-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for latency clusters in a sentence and distributions per table row. For raw marks use RugStrip; for a series over
time, Sparkline.
## Sizing [#sizing]
**where you fall**
```tsx
const times = Array.from({ length: 120 }, (_, i) =>
i % 3 === 0 ? 40 + (i % 10) : 20 + ((i * 7) % 60),
);
```
**fixed edges across rows**
```tsx
const times = Array.from({ length: 120 }, (_, i) =>
i % 3 === 0 ? 40 + (i % 10) : 20 + ((i * 7) % 60),
);
```
## Variants [#variants]
```tsx
i % 3 === 0 ? 40 + (i % 10) : 20 + ((i * 7) % 60),
);
`"
>
```
`markValue` marks the bin its value falls in and never re-bins around it.
```tsx
`"
>
```
The accessible summary states the modal bin's edges through `format`/`locale` — under `de-DE` a value like 24000 reads
"24.000" (period as the thousands separator) instead of the English "24,000".
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
An empty series renders zero bars and an accessible name of "No data." rather than a misleading flat line. A constant
series collapses to ONE full-height bin instead of the usual up-to-12 slivers an equal-width binner would draw for a
zero-span domain, and an explicit `bins` count collapses to the observation count when you ask for more bins than you
have values.
## Four homes [#four-homes]
**In a sentence**
```tsx
API latency this hour{" "}
{" "}
— most calls land 40–50 ms, a few tail past 70.
```
**In a table cell**
```tsx
15 + ((i * 4) % 15))} width={64} height={18} />
```
**In a KPI card**
```tsx
46 ms
median
```
**In a tab header**
```tsx
API
```
## Accessibility [#accessibility]
The accessible name names the modal bin: **"120 values, most between 42.09 and 47.36."** The interactive entry roves
bins with range announcements (**"42.09 to 47.36: 26 values."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Raw observations. |
| `bins` | `number` | Bin count; auto = min(12, √n). |
| `markValue` | `number` | A VALUE whose bin gets accent. |
| `domain` | `[number, number]` | Fixed bin edges across multiples. |
| `format` | `Intl.NumberFormatOptions \| fn` | Formats the bin edges named in the summary. |
| `locale` | `string \| string[]` | BCP 47 locale(s) for the formatted bin edges. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Honeycomb (/docs/charts/honeycomb)
34 of 40 seats are taken: Honeycomb fills 34 cells of an area-filling hex grid. The unit is the cell, so the count is
countable, and this stays occupancy of a capacity rather than a magnitude. Cell size never changes with value, only the
count does. It fills row-major from the top left, so occupancy reads as a sweep, and the whole grid is exactly two SVG
paths (filled and empty) no matter how large the total.
```tsx
```
## Install [#install]
```tsx
import { Honeycomb } from "@microcharts/react/honeycomb";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Honeycomb } from "@microcharts/react/honeycomb/interactive";
```
## When to use it [#when-to-use-it]
Use it for seats or licenses taken of a capacity, an occupancy read in a KPI card, or a countable of-total in a cell.
For a capacity over about sixty use `Progress`, and for a magnitude with no total, MiniBar. It isn't a trend chart, and
`PictogramRow` is the one that counts unlike things.
## Sizing [#sizing]
**strip mode for a table cell**
```tsx
```
**blank empties for a quieter, uncluttered surface**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
Empty cells are outlined by default, so takenness survives grayscale: a filled cell against an outlined one reads
without color. `empty="blank"` drops the empty cells entirely instead of dimming them, for surfaces where the outline
reads as noise. Packing is automatic, because a near-square comb is the recognizable form; `rows` overrides it.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
A value past the total fills every cell while the accessible name keeps the true number, so occupancy is never silently
clipped. Above sixty cells the counting stops working, so the chart emits a dev warning; reach for `Progress` there.
## Four homes [#four-homes]
**In a sentence**
```tsx
Seat occupancy{" "}
{" "}
— 34 of 40 taken, one row left.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
34/40
seats taken
```
**In a tab header**
```tsx
Room A
```
## Accessibility [#accessibility]
The accessible name is the exact occupancy — **"45 of 40 seats filled."** — always the true value, even when it exceeds
the total. The interactive entry announces the count on change through a polite live region and reveals the value and
total on hover. Focus it and the arrow keys rove cell by cell (←/→ within a row, ↑/↓ holding the column), each cell
announced as **"Cell 7 of 40 — filled."**
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | Filled count (fractional rounds). |
| `total` | `number` | Capacity = cell count (default 10). |
| `rows` | `number \| "auto"` | auto (near-square) or a number; 1 = strip. |
| `empty` | `"outline" \| "blank"` | How empty cells render (default outline). |
| `unit` | `string` | Noun for the summary (e.g. "seats"). |
| `label` | `"none" \| "count" \| "percent"` | Centered readout when the comb has room (default "none"). |
| `cell` | `number` | Hex cell radius in viewBox units (default 4). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Horizon (/docs/charts/horizon)
Horizon prints a whole wide-range series in a 14-pixel row. It cuts the series into bands and folds them, so layer
opacity carries magnitude and extremes stay visible at heights where a sparkline would flatten into noise. Two folds is
the default, because two read approximately without training; three trade learnability for density, so reserve them for
ranges that genuinely span them.
**How to read it** — every band rises from the row's bottom edge, positive or negative; darker means farther from the
baseline, not which direction. Above-baseline values shade in the accent color, below-baseline in the negative color, so
direction is never color-alone even though both fold upward the same way.
```tsx
```
## Install [#install]
```tsx
import { Horizon } from "@microcharts/react/horizon";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Horizon } from "@microcharts/react/horizon/interactive";
```
## When to use it [#when-to-use-it]
Use it for dense monitoring rows (dozens stacked) and wide-range series in tight cells. Folding needs a key, so skip it
for first-glance audiences; with a few rows and room to spare, use Sparkline.
## Sizing [#sizing]
**monitoring rows**
```tsx
{hosts.map((h) => (
))}
```
**fold around a target**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
`baseline` is authored, never inferred: a fold origin is a claim about what "normal" means.
```tsx
`"
>
```
`format` also takes `Intl.NumberFormatOptions` — with a `locale`, the accessible summary's range and last-value numbers
follow that locale's own grouping ("4.500" in German, not "4,500"). The interactive readout's per-point values localize
the same way; the folded geometry never changes.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
Negative values take the negative token and fold from the same bottom edge as positive values (the default
`mode="mirror"`). Both directions get darker as they move away from the baseline, so density means distance from the
baseline rather than sign. Identical values across the whole row still render as a solid block rather than vanishing,
keeping "flat" visibly distinct from "no data."
## Four homes [#four-homes]
**In a sentence**
```tsx
Cluster load this hour{" "}
{" "}
— peaked near 45 before dropping under 10, now steady at 20.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
45
now 20
```
**In a tab header**
```tsx
web-01
```
## Accessibility [#accessibility]
The accessible name reads the unfolded series — **"Trending up 900%. Range 2 to 45. Last value 20."** — so screen-reader
users get the true values, not the folded geometry. The interactive readout announces unfolded values per point.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `(number \| null)[]` | Series over time. |
| `folds` | `2 \| 3` | Band count — 3 only when the range genuinely spans it. |
| `mode` | `"mirror" \| "offset"` | Mirror flips negatives upward (denser); offset keeps up/down. |
| `baseline` | `number` | Fold origin (e.g. a target level) — authored, never inferred. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Hourglass (/docs/charts/hourglass)
A session expires in nine minutes, and you need both halves of that: how much is gone and how much is left. Hourglass
shows both. Sand fills the top chamber for remaining and the bottom for elapsed, and both areas are **area-true**: a
linear-height fill in a triangular bulb would overstate early progress by up to 2×, so the geometry solves for true
proportional area instead. `value` is the elapsed fraction, the same polarity as `Progress`, so the two compose in one
product without re-learning which way is which. A thin stream at the neck renders while `0 < value < 1` and marks the
running state; it disappears at both ends, so finished and not-started are shape-distinct.
```tsx
```
## Install [#install]
```tsx
import { Hourglass } from "@microcharts/react/hourglass";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Hourglass } from "@microcharts/react/hourglass/interactive";
```
## When to use it [#when-to-use-it]
Use it for a deadline or session-expiry read in a sentence, a TTL cell where remaining is the story, or a time-boxed tab
or countdown. For exact percentages use Progress, for trends use Sparkline, and for fractions that aren't time use a
chart that doesn't imply a clock.
## Sizing [#sizing]
**TTL cell — remaining is the story**
```tsx
```
**finished is shape-distinct (no stream)**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`"
>
```
```tsx
`">
```
Out-of-range values clamp to 0–1 in the drawing and in the accessible name, and `NaN` renders as 0 elapsed. Both ends
drop the neck stream, so a not-started or finished glyph never reads as a running timer. In the static entry the stream
is a state mark and is never animated.
## Four homes [#four-homes]
**In a sentence**
```tsx
Session time remaining{" "}
{" "}
— 75% elapsed, renew soon.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
75%
elapsed
```
**In a tab header**
```tsx
Admin
```
## Accessibility [#accessibility]
The accessible name carries both sides: a glyph above reads **"70% elapsed, 30% remaining."** The interactive entry
cross-fades the sand levels on change, with opacity plus a scale-from-the-floor settle rather than a path interpolation.
It announces only when the value crosses 50, 90, or 100%, so a streaming value never floods a screen reader.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | Elapsed fraction 0–1 (like Progress). |
| `stream` | `boolean` | The running-sand cue (default true). |
| `label` | `"none" \| "remaining" \| "elapsed"` | Print the percent that matters to the context. |
| `fontSize` | `number` | Type size of the percent numeral, in viewBox units (default 8). |
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](/docs/quickstart#the-shared-grammar).
# Hypnogram (/docs/charts/hypnogram)
Hypnogram draws which state a system was in over time: each state is a flat run at its own height, left to right, joined
by right-angle steps. There are no diagonals: a state holds until the next one, so a sloped line would invent readings
that never happened. Run lengths and the number of steps carry how choppy the period was. The `states` order sets the
vertical axis, and order is meaningful. Pass it explicitly for ordinal states like sleep depth or incident severity; for
nominal states with no rank, `mode="lanes"` keeps the axis from implying one.
```tsx
`"
>
```
## Install [#install]
```tsx
import { Hypnogram } from "@microcharts/react/hypnogram";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Hypnogram } from "@microcharts/react/hypnogram/interactive";
```
## When to use it [#when-to-use-it]
Use it for sleep stages and for deploy, machine, or incident state over time. For continuous signals use Sparkline; for
a single current state use StatusDot.
## Sizing [#sizing]
**table cell**
```tsx
```
**lanes (nominal)**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
A single entry holds its state across the whole domain. That is one flat run, and the summary names it directly: **"1
state, no transitions; Deep throughout."** A state present in the data but missing from an explicit `states` order is
never dropped. It is appended as its own row, with a dev-only console warning, so the strip accounts for every state it
is given.
## Four homes [#four-homes]
**In a sentence**
```tsx
Last night's sleep{" "}
{" "}
— two deep blocks.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
2 deep
```
**In a tab header**
```tsx
Mon
```
Best at KPI/card scale — stage steps need height to separate.
## Accessibility [#accessibility]
The accessible name states the shape: **"3 transitions across 4 states; longest run Awake."** The interactive entry
roves the runs, and each announces its state and span (**"Light, from 8 to 22."**). Its entrance is a left-to-right clip
reveal of the finished trace; smoothing or easing the step corners would change what the data says, so the steps are
never interpolated.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ t, state }[]` | State holds from t to the next entry. |
| `states` | `string[]` | Row order top→bottom; ordinal semantics live here. |
| `emphasis` | `string` | Accents one state — the decision read. |
| `mode` | `"steps" \| "lanes"` | Lanes for nominal states with no rank. |
| `connectors` | `boolean` | Vertical transition strokes (default true); off for ultra-dense strips. |
| `labels` | `boolean` | Left-gutter state names (default: on when width ≥ 96). |
| `colors` | `string[]` | Per-state lane colours (lanes mode), cycled; overrides --mc-cat-N. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# IconArray (/docs/charts/icon-array)
IconArray makes a stated rate countable: it prints **3 in 20** beside a grid of twenty units with three of them filled.
`total={20}` and `label="ratio"` are the default pairing because "3 in 20" reads faster for a lay audience than "15%",
and the ratio label plus the fixed grid keep the denominator on the page instead of in the reader's head. Units fill in
contiguous reading order from the top-left: medical-risk-communication research finds scattered fills measurably harder
to count, and counting is the whole point of this chart. There are **no partial-unit fills ever**. A 37% rate is never a
unit drawn 37% full, and a rate too small to fill one unit is flagged rather than faked.
```tsx
```
## Install [#install]
```tsx
import { IconArray } from "@microcharts/react/icon-array";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { IconArray } from "@microcharts/react/icon-array/interactive";
```
## When to use it [#when-to-use-it]
Use it for risk in a sentence, uptake and adoption rates, and lay-audience probabilities. For a trend use Sparkline; for
a full distribution use QuantileDots.
## Sizing [#sizing]
**1 in 10 framing**
```tsx
```
**risk polarity**
```tsx
// fewer is better → filled units read as the risk
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
```tsx
`">
```
A rate of exactly 0 draws every unit hollow. A rate that is real but rounds to 0 whole units (`note: "sub"`) also shows
a hollow grid, and the summary says so rather than standing a fractional unit in for "almost none." `value` is clamped
to `[0, 1]` and non-finite input renders as 0, so a bad upstream number never breaks the grid.
## Four homes [#four-homes]
**In a sentence**
```tsx
Adverse event rate{" "}
{" "}
— 3 of 20 patients, 15%.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
15%
3 of 20
```
**In a tab header**
```tsx
Cohort A
```
## Accessibility [#accessibility]
The accessible name gives the count and the rate: **"3 in 20. About 15%."** Degenerate rates are spelled out too ("0 in
20", "20 in 20 — all."). The interactive entry roves the grid in reading order with the arrow keys (2-D, row-major),
announcing each unit's state and the running count: **"Unit 1 of 20 — filled. 3 of 20 filled."**
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | The rate, 0–1. |
| `total` | `10 \| 20 \| 100` | Denominator / grid size (default 20). |
| `label` | `"ratio" \| "percent" \| "none"` | "3 in 20" (default) reads better than "15%" for lay audiences. |
| `shape` | `"square" \| "round" \| "dot"` | Shared cell vocabulary (default square). |
| `positive` | `"up" \| "down"` | Polarity — down (fewer is better) flips the fill to the risk tone. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# All charts (/docs/charts)
One grammar runs through all of them: `data` alone always renders something correct, and a prop name means the same
thing on every chart. So you don't pick by chart — you pick by the **decision** you need read at a glance. The chooser
below is filed exactly that way: start from the question, land on the chart.
- [Browse all charts](/charts)
## Static and interactive [#static-and-interactive]
Nearly every chart ships two entries — a hook-free **static** default (server-component safe, zero client JavaScript)
and an **interactive** twin from a `/interactive` subpath. WindBarb is the lone static-only exception. Every chart page
lists the import paths it has; the [Quickstart](/docs/quickstart#add-interactivity) covers when to reach for which, and
[Accessibility](/docs/accessibility#one-interaction-contract) documents the one interaction contract they all share.
# LikertStrip (/docs/charts/likert-strip)
LikertStrip stacks ordinal responses on either side of a center line: disagree to the left, agree to the right. Graded
opacity encodes ordinal distance from neutral, never magnitude. Where SegmentedBar shows composition without valence,
this reads the lean and how hard it leans. Both neutral conventions ship. `neutral="split"` straddles the center line,
the canonical placement; `neutral="omit"` takes neutral out of the bar for a cleaner pole comparison, and its share is
still counted in the total and spoken in the accessible summary.
```tsx
```
## Install [#install]
```tsx
import { LikertStrip } from "@microcharts/react/likert-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { LikertStrip } from "@microcharts/react/likert-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for survey question rows (share one scale via SparkGroup) and sentiment in cards. Avoid it past 7 levels; for
exact per-level values use MiniBar.
## Sizing [#sizing]
**survey rows**
```tsx
{questions.map((q) => (
))}
```
**net score for dense tables**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
Empty data, or data whose values all resolve to 0, has nothing to divide into a diverging read. No bar draws, and the
accessible name says so plainly: **"No responses."** When every non-zero response lands on the neutral level, the bar is
entirely the center segment and the summary reads **"All responses neutral."** instead of forcing a lean out of no
signal. Negative counts are treated as 0 rather than pushed across the center line onto the wrong side, and the static
entry logs a dev warning when it happens.
## Four homes [#four-homes]
**In a sentence**
```tsx
Checkout satisfaction, Q1{" "}
{" "}
— 62% agree, 24% disagree. Leans positive.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
62%
agree, 24% disagree
```
**In a tab header**
```tsx
New users
```
## Accessibility [#accessibility]
The accessible name carries the full valence read: **"62% agree, 24% disagree, 14% neutral. Leans positive."** A |net|
under 5 points reads "Balanced." The interactive entry steps the levels in data order and announces the response count
beside the share it was computed from: **"Disagree: 14% (14), level 2 of 5."**
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; value }[]` | Ordinal levels, negative → positive. |
| `neutral` | `"split" \| "omit"` | Center-straddle or omit-from-bar (always labeled). |
| `label` | `"ends" \| "net" \| "none"` | Agree/disagree % or one signed score. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# MicroBox (/docs/charts/micro-box)
MicroBox prints a five-number summary in a table row: min, Q1, median, Q3, max. Whiskers run to the min and max by
default, because Tukey fences imply a normality assumption most product data doesn't meet. `whiskers="tukey"` switches
to those fences and exposes fence-breakers as dots, capped at 3 per side (the furthest). Precomputed `stats` whose
values aren't monotonic are refused with a dev warning rather than drawn as a plausible-looking box.
```tsx
```
## Install [#install]
```tsx
import { MicroBox } from "@microcharts/react/micro-box";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { MicroBox } from "@microcharts/react/micro-box/interactive";
```
## When to use it [#when-to-use-it]
Use it for latency percentile rows (p50/p95/p99) and spread beside a stat; precomputed `stats` is the production path.
For distribution shape use HistogramStrip. Fewer than 5 observations render as dots rather than a box.
## Sizing [#sizing]
**precomputed stats (production path)**
```tsx
```
**shared domain rows**
```tsx
```
## Variants [#variants]
```tsx
40 + i), 400, 500];
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
Under 5 observations MicroBox draws the raw values as dots, never a box built from too few of them. An all-equal series
has a degenerate IQR: the box collapses to a 1-unit tick, and the domain pads ±1 around the value so the mark stays
centered and visible.
## Four homes [#four-homes]
**In a sentence**
```tsx
Checkout latency this week{" "}
{" "}
— median 110ms, tail past 300ms.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
42 ms
median, IQR 35–51
```
**In a tab header**
```tsx
Prod
```
## Accessibility [#accessibility]
The accessible name is the five-number reading: **"Median 50.5, middle half 45.25 to 55.75, range 40 to 500."** The
interactive entry roves a fixed 5-stop model (min → Q1 → median → Q3 → max) and announces each stat (**"Median: 42."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` | `number[]` | Raw observations (exclusive with stats). |
| `stats` | `{ min; q1; median; q3; max }` | Precomputed server aggregates. |
| `whiskers` | `"minmax" \| "tukey"` | Tukey exposes outliers as dots. |
| `outliers` | `boolean` | Render outlier dots in tukey mode. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# MicroDonut (/docs/charts/micro-donut)
A KPI card prints "62% Chrome" and wants a mix icon beside it. MicroDonut is that icon: a ring at icon size showing
roughly what something is made of. For any comparative read, use [SegmentedBar](/docs/charts/segmented-bar) instead — a
flat bar beats a donut of the same data at every size we ship. The wedge cap and the labeled rollup are non-optional,
and so is the hole: a ring encodes angle and arc length together, which is the read a pie's area can't match, and the
reason pie stays unshipped. Nothing here is exploded, tilted, or shadowed.
```tsx
```
## Install [#install]
```tsx
import { MicroDonut } from "@microcharts/react/micro-donut";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { MicroDonut } from "@microcharts/react/micro-donut/interactive";
```
## When to use it [#when-to-use-it]
Use it for a mix icon beside the printed headline number in a KPI card. For any comparative read use SegmentedBar; these
docs steer there first.
## Sizing [#sizing]
**beside the printed number**
```tsx
// the number is the datum; the donut repeats it — declare it decorative
62% Chrome
```
**accessible mix icon**
```tsx
```
## Variants [#variants]
```tsx
62% Chrome `"
>
{"62% Chrome "}
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
A single category fills the ring, and that full annulus looks like a 100% ProgressRing; the summary names the category
and its share, which is what tells the two apart. Empty data draws no wedges.
## Four homes [#four-homes]
**In a sentence**
```tsx
// the number is the datum; the donut repeats it — declare it decorative
Chrome carried 62% of this week's sessions{" "}
{" "}
— the rest split across four browsers.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
1,000
62% Chrome
```
**In a tab header**
```tsx
Desktop
```
## Accessibility [#accessibility]
The accessible name is the full composition, worded the same as SegmentedBar. `decorative` marks the chart `aria-hidden`
for the one sanctioned ornamental use, beside a printed value, and the interactive entry refuses to make a decorative
donut a tab stop.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; value }[]` | Parts of the whole. |
| `maxWedges` | `number` | Rollup threshold (default 4). |
| `decorative` | `boolean` | Redundant ornament beside a printed value → aria-hidden. |
| `weight` | `number` | Annulus thickness (shared with ProgressRing). |
| `label` | `"none" \| "total"` | Center total when the hole has room (default "none"). |
| `colors` | `string[]` | Per-wedge colours, cycled; overrides --mc-cat-N. Other stays neutral. |
| `size` | `number` | Donut square edge in viewBox units (default 24). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# MicroScatter (/docs/charts/micro-scatter)
MicroScatter plots one dot per x/y pair to show whether two metrics move together. Dots render at 75% opacity, so
overplot reads as density instead of hiding points behind each other. Duplicates are never jittered, because position is
the encoding. Axes go unlabeled at this scale, so `title` has to name both variables, the way the demo below does. The
summary's relationship words come from a documented heuristic on |r| (≥ 0.7 strong, ≥ 0.4 moderate, ≥ 0.2 weak), and
wherever a relationship word appears, r appears beside it.
```tsx
{"Latency and error rate "}
{" correlate strongly."}
```
## Install [#install]
```tsx
import { MicroScatter } from "@microcharts/react/micro-scatter";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { MicroScatter } from "@microcharts/react/micro-scatter/interactive";
```
## When to use it [#when-to-use-it]
Use it for correlation in a sentence and two-metric relationships in KPI cards. Past 60 points, bin the data instead;
for ordered time series use Sparkline.
## Sizing [#sizing]
**in a sentence**
```tsx
latency and errors correlate strongly
```
**with the trend**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
Empty data draws just the frame, with "No data." as the summary. Under 3 points, or with a zero-variance cloud, the
summary states the count and stops: no correlation claim without enough evidence behind it. Coincident points are never
jittered apart. They overlap exactly, and at 75% dot opacity the overlap reads as extra density rather than hiding a
point.
## Four homes [#four-homes]
**In a sentence**
```tsx
Bundle size and paint time{" "}
{" "}
— heavier bundles paint slower, r 0.93.
```
**In a table cell**
```tsx
// concurrent requests vs p95 latency, per service
{services.map((s) => (
{s.name}
))}
```
**In a KPI card**
```tsx
0.93
r, strong positive
```
**In a tab header**
```tsx
Paid r 0.93
```
## Accessibility [#accessibility]
The accessible name is the count plus the evidence-backed relationship: **"24 points. Strong positive relationship (r
0.93)."** With fewer than 3 points, or a zero-variance cloud, it makes no claim at all and reads just **"2 points."**
The interactive entry steps points ordered by x and announces each formatted pair.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ x; y }[]` | Unordered pairs. |
| `trend` | `boolean` | Least-squares line — linear only, never smoothed. |
| `focal` | `number` | Accent one point — "this one, among all of them". |
| `xDomain` | `[number, number]` | X scale (domain keeps its grammar meaning: y). |
| `domain` | `[number, number]` | Y scale — the shared grammar name, paired with xDomain. |
| `r` | `number` | Dot radius, clamped [1, 3]. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# MiniBar (/docs/charts/mini-bar)
MiniBar draws one zero-anchored bar per category, in the order the data arrives, so a row of categories reads at a
glance: which is biggest, and by roughly how much. That order is often information itself: weekday order and funnel
order carry meaning that ranking would destroy, so sorting is an explicit `order` prop and never silent. An explicit
`domain` is widened to include zero, because a bar whose length doesn't start at zero misstates its own magnitude, and
the component refuses to draw one.
```tsx
```
## Install [#install]
```tsx
import { MiniBar } from "@microcharts/react/mini-bar";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { MiniBar } from "@microcharts/react/mini-bar/interactive";
```
## When to use it [#when-to-use-it]
Use it for per-row category mix in tables and small comparisons in KPI cards. Past 8 categories reach for a full bar
chart; for time series use SparkBar.
## Sizing [#sizing]
**table cell**
```tsx
```
**signed with polarity**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`"
>
```
The extremes named in the summary follow `format`/`locale`: 12400 reads "12.400" under `de-DE` rather than the English
"12,400".
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
A `null` value draws no bar but keeps its band, so the two real bars around it stay exactly as far apart as in a fully
populated row, and a column of MiniBars stays aligned through missing data. A single category still renders, and its
summary says so plainly ("1 category…") rather than degenerating into a bare-bar special case. Past 8 categories every
bar still renders, with nothing dropped or truncated, but the component logs a dev warning: MiniBar is a cell chart, and
a wider comparison belongs in a full bar chart.
## Four homes [#four-homes]
**In a sentence**
```tsx
Q3 revenue splits four ways{" "}
{" "}
— East alone outsells North more than seven to one.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
East
45% of Q3 revenue
```
**In a tab header**
```tsx
Region
```
## Accessibility [#accessibility]
The accessible name gives the count and the extremes: **"4 categories. Highest East 940, lowest North 120."** The
interactive entry announces each bar with its rank: **"East: 940 — 1st of 4."**
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; value }[]` | Categories in meaningful order. |
| `order` | `"data" \| "desc" \| "asc"` | Ranking read vs positional read — data-facing, not styling. |
| `highlight` | `number \| string` | Index or label to emphasize. |
| `orientation` | `"horizontal" \| "vertical"` | Rows for wider, shorter cells. |
| `positive` | `"up" \| "down"` | Engages pos/neg tokens on signed data. |
| `label` | `"none" \| "max"` | Peak-value readout (vertical only; default "none"). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# MinimapStrip (/docs/charts/minimap-strip)
You are 300 lines into a 1,200-line log, three search hits sit elsewhere in it, and the tail hasn't loaded yet.
MinimapStrip shows all three at once: a content thumbnail under a viewport window, a separate lane of annotation ticks,
and hatched fog over any region you haven't loaded or crawled. Keeping position and annotations in two lanes keeps them
as two reads instead of one muddle. Fog is a first-class state rather than a blank stretch that would read as empty
content, and the unknown share is disclosed in the accessible name. The window maps linearly to the domain; there is no
fisheye to distort where you are.
```tsx
```
## Install [#install]
```tsx
import { MinimapStrip } from "@microcharts/react/minimap-strip";
Math.abs(Math.sin(i / 40)) + Math.abs(Math.sin(i / 150)) * 0.6,
),
window: [520, 660],
marks: [100, 600, 1100],
known: [[0, 1104]],
}}
title="Document position"
/>
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { MinimapStrip } from "@microcharts/react/minimap-strip/interactive";
import { useState } from "react";
const [viewport, setViewport] = useState<[number, number]>([520, 660]);
```
## When to use it [#when-to-use-it]
Use it for document or log position and long-timeline navigation. For a single value use Progress; for exact content
values use Sparkline.
## Sizing [#sizing]
**log viewer cell**
```tsx
```
**heat**
```tsx
```
## Variants [#variants]
```tsx
Math.abs(Math.sin(i / 40)) + Math.abs(Math.sin(i / 150)) * 0.6,
),
window: [300, 440],
marks: [600, 1000],
}}
mode="heat"
/>`"
>
```
## Edge cases [#edge-cases]
```tsx
Math.abs(Math.sin(i / 20))),
window: [40, 90],
known: [[0, 200]],
}}
/>`"
>
```
When `known` covers the whole domain, no fog renders at all.
## Four homes [#four-homes]
**In a sentence**
```tsx
Scroll position in log{" "}
{" "}
— at line 660 of 1,200, dense middle section.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
55%
through document
```
**In a tab header**
```tsx
Errors
```
## Accessibility [#accessibility]
The accessible name places you in the whole: **"Viewing 12% of the whole (300–440 of 1,200); 2 marks."** The interactive
entry is a slider. Drag or click to move the window, or nudge it with ←/→ (Shift for a bigger jump). On hover, focus, or
drag, the window's own edges float above it as a chip (`400–500`), the same range `aria-valuetext` reports, so a sighted
reader and a screen-reader reader get the same numbers. `readout={false}` drops the chip.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ content, window, marks?, known? }` | Density series, viewport, ticks, covered regions. |
| `mode` | `"bars" \| "heat"` | Heat is a calmer opacity strip. |
| `markLane` | `boolean` | Dedicated tick lane vs overlaying ticks. |
| `onWindowChange` | `(window: [number, number]) => void` | (interactive) Fires with the new `[start, end]` index range as the brush window is dragged. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# MoonPhase (/docs/charts/moon-phase)
MoonPhase lights a fraction of a disc, a form most people read without a legend. The lit **area** equals the value
exactly, via a closed-form terminator rather than the phase-angle approximation that under-lights mid-cycle, so 50%
lights half the disc. Progress mode is the default and fills monotonically, the way a sprint, quota, or billing period
does. The real lunar cycle waxes and then wanes, so `mode="cycle"` is a separate, explicit data-semantic switch rather
than a preset. Area is a medium-precision channel: for exact reads use `Progress`.
```tsx
```
## Install [#install]
```tsx
import { MoonPhase } from "@microcharts/react/moon-phase";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { MoonPhase } from "@microcharts/react/moon-phase/interactive";
```
## When to use it [#when-to-use-it]
Use it for sprint or quota progress in a sentence, a billing-period or release-cycle marker, or any 0–1 completion. For
exact percentages use Progress, for trends use Sparkline, and for comparisons use MiniBar.
## Sizing [#sizing]
**cycle mode maps the real lunar phases**
```tsx
// full moon
```
## Variants [#variants]
```tsx
// first quarter
// full moon`"
>
```
## Edge cases [#edge-cases]
```tsx
// clamps to 0 — new, dark disc
// clamps to 1 — full, lit disc`"
>
```
Values outside 0–1 clamp instead of overflowing: −0.4 draws a new, dark disc and 1.6 a full, lit one.
## Four homes [#four-homes]
**In a sentence**
```tsx
Sprint progress{" "}
{" "}
— 68% through, waxing past half.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
68%
```
**In a tab header**
```tsx
Sprint 12
```
## Accessibility [#accessibility]
The accessible name states the fraction: **"0% of the cycle complete."** in progress mode, or "0% through the cycle." in
cycle mode. The interactive entry fades the lit region back in on change with a slight bloom — opacity plus a small
scale, never a path interpolation, and skipped entirely under `prefers-reduced-motion`. It reveals the exact percent on
hover or focus, and announces changes through a polite live region throttled to at most once a second.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | Fraction 0–1 (clamped). |
| `mode` | `"progress" \| "cycle"` | progress = monotonic fill; cycle = true lunar mapping (0 new → 0.5 full → 1 new). |
| `size` | `number` | Disc box edge in viewBox units (default 16). |
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](/docs/quickstart#the-shared-grammar).
# MusicStaff (/docs/charts/music-staff)
MusicStaff reads a short series as a melody, so its shape lands in one look: each value is a note on a five-line staff,
**pitch** (vertical position) is the value, and left-to-right is time. Pitch is the only channel, so there are no clefs,
stems, beams, or bar lines. The read is in steps (nine to thirteen positions), so exact values steer to `Sparkline` with
a label. Two adjacent equal values are spaced along the time axis and never dodged vertically, because moving a note
vertically would change its pitch.
```tsx
`"
>
```
## Install [#install]
```tsx
import { MusicStaff } from "@microcharts/react/music-staff";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { MusicStaff } from "@microcharts/react/music-staff/interactive";
```
## When to use it [#when-to-use-it]
Use it for a weekly-rhythm read in a sentence, the shape of a sprint or short series in a cell, or a per-channel melody
in a tab. For exact values use Sparkline with a label; skip it for long series (over sixteen points) and for trends
where the exact slope matters.
## Sizing [#sizing]
**staff mode clamps the pitch on-staff**
```tsx
```
**a rest (null) leaves a gap**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
Ledger mode is the default: a note above or below the staff sits on a hairline ledger tick and keeps its true pitch,
rather than being clipped. `mode="staff"` is for dense cells where ledger lines would collide with a neighbour; there,
out-of-range pitches clamp to the nearest staff line or space, trading visual precision for a collision-free cell. The
accessible summary is the same either way, since it describes the raw values, not the clamped pixel positions.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
```tsx
`">
```
A single value draws one note and no contour line, since a melody needs at least two notes to have a shape. An
all-`null` series draws the bare staff with no notes at all, and the accessible name falls back to `describeSeries`'s
own no-data phrasing. With a `locale`, the trailing `label="last"` value follows that locale's own grouping and decimal
marks.
## Four homes [#four-homes]
**In a sentence**
```tsx
Sprint velocity{" "}
{" "}
— peaked week 4.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
11
```
**In a tab header**
```tsx
Platform
```
## Accessibility [#accessibility]
MusicStaff reuses `Sparkline`'s natural-language summary verbatim. For `[3, 5, 4, 8, 6, 9]` that is **"Trending up 200%.
Range 3 to 9. Last value 9."** The interactive entry steps the notes with ←/→ and announces each as **"Point 3 of 6:
4."**
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `(number \| null)[]` | The series; null = a rest. |
| `mode` | `"staff" \| "ledger"` | ledger (±2, default) or staff (clamp on-staff). |
| `label` | `"none" \| "last"` | Print the final value after the last note. |
| `fontSize` | `number` | Type size of the last-note label, in viewBox units. Defaults from `height`. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# NetFlow (/docs/charts/net-flow)
Money came in, money went out, and the number you act on is what's left. NetFlow draws inflow as an area above a zero
baseline and outflow mirrored below it on **one shared magnitude scale**, never independently scaled to balance the
picture, with the net line (`in − out`) on top. Gross and net answer different questions, and one cell answers both.
Both areas anchor at zero, the net line is a plain `in − out` and is never smoothed, and its sign is stated in the
label's **text**, so direction is never carried by color alone.
```tsx
```
## Install [#install]
```tsx
import { NetFlow } from "@microcharts/react/net-flow";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { NetFlow } from "@microcharts/react/net-flow/interactive";
```
## When to use it [#when-to-use-it]
Use it for cash flow per account row, in vs out (signups vs churn) in a KPI card, and any in-vs-out where the net is the
decision. For a single net series use Sparkline; for one period's split use Delta or SegmentedBar.
## Sizing [#sizing]
**mirrored bars for few months**
```tsx
```
**debt paydown (outflow is the goal)**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
The net label and accessible summary both follow `format`/`locale`: 38000 reads "38.000" under `de-DE` rather than the
English "38,000".
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
A single period always renders as mirrored bars, never an area, since an area drawn through one point would imply
continuity the data doesn't have. `in`/`out` are magnitudes: a negative value is invalid input rather than a reversed
direction, so it is coerced to `0`. When every period's magnitudes are zero (all periods coerced or genuinely empty),
NetFlow draws only the zero baseline, with no areas, no bars, and no net line, and the accessible name says so plainly:
**"No flow across 1 period."**
## Four homes [#four-homes]
**In a sentence**
```tsx
Monthly cash flow{" "}
{" "}
— net positive for the third month.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
+$42K
this month
```
**In a tab header**
```tsx
Operating
```
## Accessibility [#accessibility]
The accessible name pairs the net with its gross and counts the positive periods: **"Net +14 last period; in 57 vs out
43; net positive 4 of 6 periods."** The interactive entry steps the periods and announces inflow, outflow, and the
signed net together.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ in; out }[]` | Periods, oldest first — inflow and outflow magnitudes (both ≥ 0). |
| `mode` | `"area" \| "bars"` | Mirrored areas (default) or discrete columns for few periods. |
| `net` | `boolean` | The net line (in − out). Default true. |
| `positive` | `"up" \| "down"` | Which direction is good — 'down' for debt-paydown contexts. |
| `label` | `"last" \| "none"` | Signed net in a right gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Ohlc (/docs/charts/ohlc)
Four numbers per period, in one table cell: open, high, low, close. Together they say how far a session ran and which
way it closed, one row per instrument. Direction comes from valence color and from the body geometry, so it never rests
on color alone. `maxPeriods` renders the most recent N sessions and warns in development past that — extra periods are
dropped visibly, never averaged into composite candles.
```tsx
`"
>
```
## Install [#install]
```tsx
import { Ohlc } from "@microcharts/react/ohlc";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Ohlc } from "@microcharts/react/ohlc/interactive";
```
## When to use it [#when-to-use-it]
Use it for watchlist table rows and ticker KPI cards. For a single close series use Sparkline, and keep the count under
about 20 periods.
## Sizing [#sizing]
**watchlist rows**
```tsx
{tickers.map((t) => (
))}
```
**bars with last close**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
A session that opens and closes at the same price renders neutral and keeps a minimum 1-unit body height, so a flat
period stays visible instead of collapsing to a hairline. A single period renders its own bar against its own range.
## Four homes [#four-homes]
**In a sentence**
```tsx
ACME closed the session at{" "}
{" "}
$150.30, up 7.4% over 20 sessions.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
$150.30
up 7.4% over 20 sessions
```
**In a tab header**
```tsx
ACME
```
## Accessibility [#accessibility]
The accessible name is the run read — **"20 periods. Last close 150.3, up 7.4%; range 136.4 to 156."** The interactive
entry announces each session's four prices (**"Period 18 of 20: open 145.6, high 150.6, low 141.6, close 144.1."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ open; high; low; close }[]` | Periods, oldest first. |
| `mode` | `"candle" \| "bars"` | Candle bodies or open/close ticks. |
| `maxPeriods` | `number` | Renders the most recent N (never averaged). |
| `label` | `"last" \| "none"` | Last close in a right gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# OrbitStatus (/docs/charts/orbit-status)
OrbitStatus encodes two numbers in one glyph. The orbit radius is latency, so a wider orbit is a slower service, and the
orbit's dash density is the call rate, so denser dashes mean more calls. In the interactive entry the satellite orbits
at a speed that mirrors the rate. Both channels are low-precision ambient reads; for exact numbers use a `Sparkline` for
latency and a `Delta` or `MiniBar` for rate. A lone orbit radius means nothing on its own, so pass explicit
`latencyDomain` and `rateDomain` — the same steer as `FatDigits`.
```tsx
```
## Install [#install]
```tsx
import { OrbitStatus } from "@microcharts/react/orbit-status";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { OrbitStatus } from "@microcharts/react/orbit-status/interactive";
```
## Motion, and reduced motion [#motion-and-reduced-motion]
The satellite's angular speed is quantized to the same five steps as the static dash density, so the motion and the
still frame decode identically. Rate lives in the dashes as well as the spin, which is what lets a paused satellite
still carry both variables. This is the deliberate idle-loop exception, allowed because the loop rate is the datum. It
pauses off-screen and, under `prefers-reduced-motion`, does not spin at all: the dash density already carries the rate.
Read the satellite's speed, not its angular position; the position encodes nothing.
## When to use it [#when-to-use-it]
Use it for a live dependency health dot in a service table, for latency and rate together in one small mark, or for an
infra status read. For exact latency or a trend over time use `Sparkline`; for exact rate use `Delta` or `MiniBar`.
## Sizing [#sizing]
One `size` prop, in viewBox units, sets the orbit box — it defaults to `20`. There is no separate `width`/`height`: the
glyph is square, and the rendered box widens only by the gutter a `label` reserves. `fontSize` follows `size` unless you
set it.
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
An unknown latency or rate renders gray, drops the satellite, and stops the spin, so an unreachable dependency never
looks healthy. A rate of `0` draws a solid, dash-free orbit. At `threshold` the satellite doubles in size. The rate is
quantized to five ordinal steps in both renderings (five dash counts and five angular periods), and radius and speed
read from the same domains in the static and interactive frames.
## Four homes [#four-homes]
**In a sentence**
```tsx
Payments API health{" "}
{" "}
— 240 ms latency, 12 req/s, orbit stable.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
240ms
· 12 rps
```
**In a tab header**
```tsx
payments
```
## Accessibility [#accessibility]
The accessible name is both variables with units — **"240ms latency at 12 calls/s."** — plus "— above threshold" when
the latency crosses `threshold`, or **"Latency unknown."** when a value is missing. The satellite's size and the summary
carry the threshold state, never color alone. Motion is gated on `prefers-reduced-motion`, and the live region announces
threshold crossings only.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `latency` (required) | `number` | Orbit radius (weak — pass a domain). |
| `rate` (required) | `number` | Dash density / satellite speed. |
| `latencyDomain` | `[number, number]` | Latency extent (insist on it — a lone radius is meaningless). |
| `rateDomain` | `[number, number]` | Rate extent (default [0, 2·rate]). |
| `threshold` | `number` | Latency threshold: at/above it the satellite doubles + the summary flags it. |
| `size` | `number` | Orbit box edge in viewBox units (default 20). |
| `fontSize` | `number` | Type size of the gutter label, in viewBox units. Defaults from `size`. |
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](/docs/quickstart#the-shared-grammar).
# PairedBars (/docs/charts/paired-bars)
East came in at 940 against a plan of 1,200, West a little over its own: PairedBars draws each value beside its
reference, category by category. Both bars in a pair share one zero-anchored domain, so their lengths are always
comparable. The reference is muted by two structural cues, opacity *and* width, never color alone.
```tsx
`"
>
```
## Install [#install]
```tsx
import { PairedBars } from "@microcharts/react/paired-bars";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { PairedBars } from "@microcharts/react/paired-bars/interactive";
```
## When to use it [#when-to-use-it]
Use it for budget vs actual per region and target vs result rows. For data without a reference series use MiniBar, and
keep it to 5 pairs or fewer.
## Sizing [#sizing]
**table cell**
```tsx
```
**overlay for tight cells**
```tsx
// ghost = the reference, never the value
```
## Variants [#variants]
```tsx
`"
>
```
Grouped is the default because overlapping bars hide small overshoots. Reach for `mode="overlay"` when the cell is too
tight for two bars per pair.
```tsx
`"
>
```
```tsx
`"
>
```
The gap named in the summary follows `format`/`locale` — 12000 reads "12.000" under `de-DE` rather than the English
"12,000".
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
A pair whose `ref` is `null` draws its value bar alone, with no ghost and no zero-width placeholder, and the interactive
entry announces it as **"East: 940, no reference."** A chart where *every* reference is missing dev-warns and points you
to MiniBar. Past 5 pairs the grouped read blurs, so the component still renders every pair and logs a dev warning.
## Four homes [#four-homes]
**In a sentence**
```tsx
Regional spend vs plan this quarter{" "}
{" "}
— East is furthest off target, 940 spent against a 1,200 budget.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
2 / 4
regions under budget, this quarter
```
**In a tab header**
```tsx
Regional
```
## Accessibility [#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](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ 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` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# ParetoStrip (/docs/charts/pareto-strip)
Give it a list of causes with their counts and ParetoStrip sorts them descending, then overlays a **cumulative-share
line on a fixed 0–100% scale** that shows how few of them you would have to fix to account for most of the total. That
scale spans the full plot height and is never rescaled, so the curve can't be steepened. Bars up to the threshold
crossing are accent — the **vital few** — and the rest are muted, which is how the chart says where to stop reading. The
80% default is a reference you can move or turn off, not a law.
```tsx
```
## Install [#install]
```tsx
import { ParetoStrip } from "@microcharts/react/pareto-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { ParetoStrip } from "@microcharts/react/pareto-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for a "fix these three" read in a KPI card, incident causes or support tags in a tab header, and any long-tail
composition where a few categories dominate. For a plain ranking use MiniBar; for parts of a single whole use
SegmentedBar.
## Sizing [#sizing]
**roll the tail into Other**
```tsx
```
**no threshold line**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
`maxItems` rolls the tail into `Other`, drawn at its true size but always last and never counted among the vital few. If
`Other` towers over the top cause, `maxItems` is set too aggressively.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
## Four homes [#four-homes]
**In a sentence**
```tsx
Incident causes{" "}
{" "}
— top 3 causes account for 80%.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
```
**In a tab header**
```tsx
deploy
```
## Accessibility [#accessibility]
The accessible name states the vital-few count and its cumulative — **"Top 1 of 3 causes account for 90% of
incidents."** — or, when there is no threshold (or no ranked cause reaches it), the top cause and its share, as in the
rolled-up demo above: **"Timeouts leads at 39%."** The interactive entry steps the bars and announces each bar's share
and running cumulative. **T** jumps to the crossing bar.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; value }[]` | Categories with magnitudes — the component sorts descending. |
| `threshold` | `number \| false` | Cumulative reference line % (default 80 — a working reference, not a law). |
| `maxItems` | `number` | Categories beyond maxItems roll up into Other (default 8, always last). |
| `unit` | `string` | Category noun for the summary (default 'causes'). |
| `metric` | `string` | Total-metric noun for the summary (default 'the total'). |
| `label` | `"count" \| "none"` | 'K of N → cum%' in a right gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# PartitionStrip (/docs/charts/partition-strip)
The top row is the whole: one bar per parent, width proportional to its share. The row beneath it holds each parent's
children, tiling the exact x-range of their parent, so parentage reads out of the alignment. Alignment is the comparison
channel here, which a treemap discards and a flat SegmentedBar drops along with the hierarchy. Two levels is a hard
limit: deeper nesting becomes texture at this size.
```tsx
`"
>
```
## Install [#install]
```tsx
import { PartitionStrip } from "@microcharts/react/partition-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { PartitionStrip } from "@microcharts/react/partition-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for bundle, storage, or budget composition, and for any two-level breakdown. Deep hierarchies are unreadable at
this size, and flat parts belong in SegmentedBar.
## Sizing [#sizing]
**storage cell**
```tsx
```
**emphasis**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
Data with no children anywhere renders as the parent row alone; the child row stays empty. Height, not width, is what
drops the labels: below the floor font size they drop out rather than overlap. Grandchildren are ignored with a dev
warning, since three levels of nesting at 24px is texture rather than information.
## Four homes [#four-homes]
**In a sentence**
```tsx
Bundle composition{" "}
{" "}
— JS is 44% of the payload.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
```
**In a tab header**
```tsx
JS
```
## Accessibility [#accessibility]
The accessible name names the biggest leaf and its parent — **"3 groups, 5 parts; largest JS → react (31% of the
whole)."** The interactive entry roves within a row with ←/→ and moves between a parent and its children with ↑/↓,
announcing each node's own value, its share of the whole, and its share of its parent. The readout chip stacks the same
figures as rows so the value is never traded away for width.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label, value?, children? }[]` | Two-level hierarchy. |
| `emphasis` | `string` | Accents one node and its lineage. |
| `labels` | `boolean` | Parent-row labels with size drop-out. |
| `colors` | `string[]` | Per-group colours, cycled; overrides --mc-cat-N. Accent/neutral roles keep. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# PercentileLadder (/docs/charts/percentile-ladder)
A service answers at 124.5 ms for half its requests and 1,661.13 ms for the slowest 1%. PercentileLadder puts both ticks
on one zero-anchored track, where the distance between them is the reading. Graduated height and accent make the
**tail** read strongest, because the tail is usually the SLO question. The origin is never cropped: cropping it would
distort every gap. A log transform for long tails is never silent — `scale="log"` applies only when every sample is
strictly positive, and an in-chart `log` tag renders whenever it does.
```tsx
i < 130
? 90 + (i % 50)
: i < 180
? 150 + ((i * 7) % 320)
: i < 196
? 480 + ((i * 11) % 900)
: 1500 + ((i * 13) % 800),
)}
title="Request latency"
/>`"
>
```
## Install [#install]
```tsx
import { PercentileLadder } from "@microcharts/react/percentile-ladder";
i < 130
? 90 + (i % 50)
: i < 180
? 150 + ((i * 7) % 320)
: i < 196
? 480 + ((i * 11) % 900)
: 1500 + ((i * 13) % 800),
)}
format={{ style: "unit", unit: "millisecond" }}
title="Request latency"
/>
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { PercentileLadder } from "@microcharts/react/percentile-ladder/interactive";
```
## When to use it [#when-to-use-it]
Use it for latency SLOs in a sentence, the tail per endpoint in tables, and payment-size spread. For the odds of an
outcome use QuantileDots; for the full distribution shape use MicroBox.
## Sizing [#sizing]
**stricter SLO percentiles**
```tsx
```
**log for long tails**
```tsx
// the transform is never silent — a log tag renders
```
## Variants [#variants]
```tsx
i < 130
? 90 + (i % 50)
: i < 180
? 150 + ((i * 7) % 320)
: i < 196
? 480 + ((i * 11) % 900)
: 1500 + ((i * 13) % 800),
);
// value labels and the summary format through Intl — "1.661 ms", not "1,661 ms"
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`"
>
```
When every sample is identical the ladder collapses to a single tick instead of stacking overlapping ones. A single
value at or below zero makes `scale="log"` fall back to linear, and no `log` tag renders, because the transform was not
applied. Below 56 px wide the tick labels drop out rather than overlap.
## Four homes [#four-homes]
**In a sentence**
```tsx
Request latency{" "}
{" "}
— p99 at 2.1 s, long tail visible.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
```
**In a tab header**
```tsx
checkout
```
## Accessibility [#accessibility]
The accessible name states every marked percentile and the tail's multiple of the median — **"p50 124.5, p90 488.1, p99
1,661.13 — the slowest 1% take 13.3× the median."** The interactive entry roves ticks with ←/→ (Home/End jump to the
ends), announcing each tick's value and its multiple of the median (**"p99: 1,661.13 — 13.3× the median."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Raw sample; quantiles are derived. |
| `ps` | `number[]` | Percentiles to mark (default [50, 90, 99], 2–4). |
| `scale` | `"linear" \| "log"` | Log for long tails (falls back on any value ≤ 0; renders a log tag). |
| `label` | `"ps" \| "values" \| "both" \| "none"` | What the tick labels state. |
| `marks` | `"tick" \| "dot"` | Tick marks (default) or dot marks — dots read calmer over dense text. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# PercentileTrace (/docs/charts/percentile-trace)
PercentileTrace traces one entity's **percentile rank** over time on a scale **locked to 0–100**. Because the axis *is*
rank, the population is constant by definition: the middle-half (**p25–75**) and near-full (**p5–95**) bands are fixed
fields, exact by construction rather than estimated from the traced series, and the only line on the chart is the entity
itself. The axis is never truncated, which would hide how much headroom or floor is left.
```tsx
`"
>
```
## Install [#install]
```tsx
import { PercentileTrace } from "@microcharts/react/percentile-trace";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { PercentileTrace } from "@microcharts/react/percentile-trace/interactive";
```
## When to use it [#when-to-use-it]
Use it for one player's or product's rank drifting over time, for whether a standing has crossed into the top or bottom
of the pack, and for a percentile KPI where the population context matters. For a raw value over time use Sparkline; for
one absolute number against a target use Bullet or Delta.
## Sizing [#sizing]
**without population bands**
```tsx
```
**a slipping standing (down is good)**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
The endpoint dot carries **valence**. By default a rising standing is good, so it turns positive; set `positive="down"`
when slipping *down* the pack is the win, as with a support ticket's backlog rank. The line already encodes direction,
so color is a redundant cue and never the only signal.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
```tsx
`"
>
```
A single reading is a lone endpoint with no line to draw. It still reports its standing and holds the accessible-name
contract. Ranks outside 0–100 are clamped to the axis (a value of `102` reads as `p100`) and warn once in development.
With a `locale`, the percentile label and every announced number follow that locale's own formatting: `de-DE` renders
`p81,5` with a comma.
## Four homes [#four-homes]
**In a sentence**
```tsx
Standing over the season{" "}
{" "}
— climbed from 40th to 81st percentile.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
```
**In a tab header**
```tsx
Team A
```
## Accessibility [#accessibility]
The accessible name states the current percentile, the change from the first reading, and how the standing moved
relative to the middle half — **"p81 now, up 41 points from the first reading; moved above the middle half."** The
interactive entry steps the readings and announces each one's percentile.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Percentile ranks 0–100, one per reading; out-of-range values are clamped. |
| `showBands` | `boolean` | Draw the fixed p25–75 and p5–95 population fields (default true). |
| `positive` | `"up" \| "down"` | Which direction is good — colors the endpoint dot (default up). |
| `label` | `"last" \| "none"` | Final percentile in a right gutter. |
| `unit` | `string` | (interactive) Reading noun for the interactive announcement (default 'step'). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# PhaseTrace (/docs/charts/phase-trace)
CPU climbs and latency follows a beat later. PhaseTrace plots the two signals against each other as a single trajectory,
so the lag becomes a loop and a regime becomes a cluster — structure two separate sparklines hide. Path order carries
time, and the current state is a directed endpoint with an arrowhead, so you read where the pair is now and how it got
there. The trail is muted and the recent tail accented, so the eye lands on the recent motion first.
```tsx
{
const t = (i / 40) * Math.PI * 2;
return { x: 55 + Math.cos(t) * 22, y: 110 + Math.sin(t - 0.9) * 40 };
})}
xLabel="CPU"
yLabel="Latency"
title="Phase portrait"
/>`"
>
```
## Install [#install]
```tsx
import { PhaseTrace } from "@microcharts/react/phase-trace";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { PhaseTrace } from "@microcharts/react/phase-trace/interactive";
```
## When to use it [#when-to-use-it]
Use it for coupled-signal phase portraits: CPU × latency, inflation × unemployment. For reading exact values use
DualSparkline; for a single series use Sparkline.
## Sizing [#sizing]
**service cell**
```tsx
```
**quadrant grid**
```tsx
```
## Variants [#variants]
```tsx
{
const t = (i / 40) * Math.PI * 2;
return { x: 55 + Math.cos(t) * 22, y: 110 + Math.sin(t - 0.9) * 40 };
})}
tail={0.4}
startDot
/>`"
>
```
`tail` sets how much of the path is accented, and `startDot` marks the first observation. You can restyle the tail,
endpoint, and arrowhead, but not remove all three: together they are what makes the time direction recoverable.
```tsx
`"
>
```
With a `locale`, the accessible summary's coordinates follow that locale's own grouping: "1.800" in German rather than
"1,800". In-chart marks carry no text, so only the announced numbers change. The axes are named in the summary and both
domains are always linear, never silently transformed.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
({ x: 40, y: 40 }))}
xLabel="CPU"
yLabel="Latency"
/>`"
>
```
A single observation draws only the accent endpoint dot: there is no history to trail and no motion for an arrowhead to
point along. When every point in the tail window is coincident (or nearly so, within 0.5% of the combined axis span),
the heading resolves to the fifth, explicit "steady" state rather than a jittery direction from noise.
## Four homes [#four-homes]
**In a sentence**
```tsx
CPU × latency phase{" "}
{" "}
— lag loop in the upper-right regime.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
```
**In a tab header**
```tsx
api
```
Best at KPI/card scale — phase loops need a square plot.
## Accessibility [#accessibility]
The accessible name states the current point and heading, with the y-axis named first — for the hero example above,
**"Latency vs CPU: now 76.729, 75.163; heading down-right."** The interactive entry steps through the trajectory in time
order with ←/→, announcing each point's index and its value on both named axes.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ x, y }[]` | Two synchronized signals, time-ordered. |
| `xLabel / yLabel` | `string` | Axis names — the summary reads them. |
| `xDomain` | `[number, number]` | Fix the x-axis range (default: the data's x-extent). |
| `domain` | `[number, number]` | Fix the y-axis range (default: the data's y-extent). |
| `tail` | `number` | Fraction of points drawn in accent (recent motion). |
| `grid` | `boolean` | Quadrant hairlines for regime reads. |
| `startDot` | `boolean` | Anchor the path's origin for full-journey reads (default false). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# PictogramRow (/docs/charts/pictogram-row)
●●●○○ — three of five units filled, a count a reader can verify by counting. Filled and hollow differ in shape as well
as opacity, and PictogramRow keeps every unit the same size: the glyph never scales with the value.
```tsx
{"The coalition holds "}
{" of the seats."}
```
## Install [#install]
```tsx
import { PictogramRow } from "@microcharts/react/pictogram-row";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { PictogramRow } from "@microcharts/react/pictogram-row/interactive";
```
## When to use it [#when-to-use-it]
Use it for seats, slots, and ratings in a sentence, and for capacity rows in tables. Past 20 units counting fails, and
continuous ratios are not a count: use Progress for both.
## Sizing [#sizing]
**inline in a sentence**
```tsx
holds of the seats
```
**star rating via renderPoint**
```tsx
} />
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
The default clips the partial unit rather than rounding it away, and draws it as a true circular segment instead of a
`clipPath` — generated ids would break server-render determinism. Use `fractional="round"` where a partial unit is
meaningless.
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
A `total` of 0 (or non-finite) draws nothing and the summary is "No data.", since there is no row to count. A negative
value renders every unit hollow, and the summary states the true number ("-2 of 4.") rather than clamping to 0. A value
past the total fills every unit, and never draws more than `total` units, but the summary keeps the true count ("9 of
8.") and dev-warns, so the overflow is never hidden.
## Four homes [#four-homes]
**In a sentence**
```tsx
The oversight committee holds{" "}
{" "}
of its 8 seats — a working majority.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
```
**In a tab header**
```tsx
Aria Desk
```
## Accessibility [#accessibility]
The accessible name is the count — **"3 of 5."** — with the context noun coming from your `title` ("Committee seats
held. 3 of 5."). The interactive entry re-announces when the value changes, and roving the units announces each one on
its own: on the 5-of-8 demo at the top of this page, the fourth unit says "Unit 4 of 8 — filled." A unit reads as empty,
filled, or, for a genuinely partial one, the percentage it holds.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | Filled units (may be fractional). |
| `total` (required) | `number` | Unit count. |
| `shape` | `"dot" \| "square"` | Squares pack tighter in table cells. |
| `fractional` | `"clip" \| "round"` | Clip shows the true partial unit; round for seat-like units. |
| `renderPoint` | `(unit) => ReactNode` | Custom unit glyph (star ratings) — the one sanctioned customization. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# PolarClock (/docs/charts/polar-clock)
PolarClock turns each value in a cycle into a radial bar at its fixed angle, growing outward from an inner baseline and
zero-anchored there. Longer bars are busier times. Midnight (or your week start) sits at 12 o'clock and the cycle runs
clockwise. The channel is length, not sector area: equal-value bars at the rim cover more area than ones near the hub,
which is why the inner radius is nonzero. Compare lengths, not wedges.
```tsx
```
## Install [#install]
```tsx
import { PolarClock } from "@microcharts/react/polar-clock";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { PolarClock } from "@microcharts/react/polar-clock/interactive";
```
## When to use it [#when-to-use-it]
Use it for the shape of a 24-hour or 7-day cycle, for when a metric is busy across that cycle, and for a compact
seasonal read in a KPI card. For exact value comparison unroll the cycle into a `SparkBar`; for a non-cyclic trend use
`Sparkline`. Keep it under a few dozen segments.
## Sizing [#sizing]
**a 7-day week, opacity mode for tiny sizes**
```tsx
```
**rotate a weekday to the top with origin**
```tsx
// Monday at 12 o'clock
```
## Variants [#variants]
```tsx
(h === 14 ? 312 : h === 4 ? 20 : 80 + h));
const week = [120, 200, 180, 210, 260, 90, 60];
`"
>
```
At very small sizes, where length is hard to judge, `mode="opacity"` switches the channel to a five-step fill — a radial
`ActivityGrid`. That is a named change of encoding, not a cosmetic one.
```tsx
(h === 14 ? 312 : h === 4 ? 20 : 80 + h));
`"
>
```
The four cardinal ticks default on because a bare ring of bars is rotationally ambiguous: without a mark for 12 o'clock
there is no way to tell where the cycle starts. They are merged into one path, so the orientation cue costs a single
node. Set `labels={false}` only when the shape itself is the whole story and the axis genuinely doesn't matter.
```tsx
(h === 14 ? 1240 : h === 4 ? 20 : 80 + h));
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
A `null` segment leaves a gap and the baseline ring shows the hole, because missing is not the same as zero. A cycle
where every value is equal draws every bar at the same length.
## Four homes [#four-homes]
**In a sentence**
```tsx
Traffic by hour{" "}
{" "}
— peak at 2pm, quiet after midnight.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
```
**In a tab header**
```tsx
Mon
```
Best at KPI/card scale — hour wedges need room to resolve.
## Accessibility [#accessibility]
The accessible name states the peak and the quiet point of the cycle — **"Peaks at 14:00 (1.240); quietest 04:00."** —
with hour labels for a 24-segment cycle and weekday names for a 7-segment one. The interactive entry lets you arrow
through the segments circularly, each announced with its label and value through a polite live region, and the accented
`now` segment carries position and color, never color alone.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `(number \| null)[]` | One value per cycle division (24 hourly, 7 daily, any n). |
| `now` | `number` | Index of the current segment to accent. |
| `inner` | `number` | Inner radius fraction r0 — the zero baseline bars grow from (default 0.35). |
| `mode` | `"length" \| "opacity"` | Radial bars (default) or fixed-length 5-step fill. |
| `origin` | `number` | Index rendered at 12 o'clock (week-start / midnight). |
| `labels` | `boolean` | Hairline cardinal ticks at 0/¼/½/¾ — the at-rest orientation cue. Default true. |
| `segmentFormat` | `(index, n) => string` | Segment index → label (default: HH:00 for n=24, weekday for n=7, else index). |
| `size` | `number` | Dial box edge in viewBox units (default 24). |
| `fontSize` | `number` | Type size of the peak label under the dial, in viewBox units. Defaults from `size`. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# ProgressRing (/docs/charts/progress-ring)
A backup is 68% done and the only room for it is a tab header. ProgressRing draws that fraction as an arc from a start
angle fixed at 12 o'clock, with butt-cut caps: rounded caps overstate small fractions, and a variable start angle makes
identical fractions look different. It is not a gauge — no needle, no red zone.
```tsx
`"
>
```
## Install [#install]
```tsx
import { ProgressRing } from "@microcharts/react/progress-ring";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { ProgressRing } from "@microcharts/react/progress-ring/interactive";
```
## When to use it [#when-to-use-it]
Use it for tab headers, KPI card corners, and cooldowns or retry timers (`sweep`). For a precise read use Progress,
where the percent label is the datum.
## Sizing [#sizing]
**tab header**
```tsx
Sync
```
**cooldown (sweep)**
```tsx
// retry timer: the remaining wedge shrinks
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`">
```
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
Past 100% the ring clamps full while the center label carries the true percent, the same contract as Progress. A
non-finite `value` announces no data instead of a fraction. A full circle is drawn as two half-arcs, since SVG cannot
draw a single 360° arc.
## Four homes [#four-homes]
**In a sentence**
```tsx
Nightly backup is{" "}
{" "}
two-thirds through.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
```
**In a tab header**
```tsx
Photos
```
## Accessibility [#accessibility]
The accessible name reuses the progress wording — **"68% complete."**, or **"32% remaining."** in sweep mode. The
interactive entry announces only at 25/50/75/100% threshold crossings, so a streaming value never spams the screen
reader.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | The progressed amount. |
| `max` | `number` | Denominator (default 1). |
| `sweep` | `boolean` | Countdown: the REMAINING wedge shrinks. |
| `weight` | `number` | Ring thickness (viewBox units). |
| `label` | `"none" \| "percent"` | Centered figure (≥ 20 px rendered). |
| `size` | `number` | Ring box edge in viewBox units (default 24). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Progress (/docs/charts/progress)
68% complete, or 3 of 5 steps: the label *is* the datum, which is why it is on by default. The bar gives the instant
read, and the track never shrinks to make room for the label — the viewBox widens instead — so fractions stay comparable
down a table column.
```tsx
```
## Install [#install]
```tsx
import { Progress } from "@microcharts/react/progress";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Progress } from "@microcharts/react/progress/interactive";
```
## When to use it [#when-to-use-it]
Use it for KPI cards, table completion columns, and step counts (`segments`). For icon-size slots use ProgressRing; for
composition use SegmentedBar.
## Sizing [#sizing]
**table column**
```tsx
// fixed width per row — fractions stay comparable down the column
```
**stepped onboarding**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`">
```
With `label="percent"` (the default) the accessible name follows `format`/`locale` too — "68 %" under `de-DE` rather
than the English "68%".
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
```tsx
0 — an empty track,
// not an invented fraction
`"
>
```
Past 100% the bar clamps at full and the label carries the true figure ("112%"). That is the one documented case where
the bar and the number disagree, and the number wins. `max <= 0` (or a non-finite `value`/`max`) renders an empty track
with no fill, and the label becomes an em dash ("—") rather than a fabricated number; the accessible name says "No
data." instead of computing a ratio it can't compute. A `value` of `0` renders a real, empty-but-present bar rather than
the no-data state, so "not started" and "unmeasurable" stay visually distinct.
`max` — not `total` — is the deliberate exception across the library. Every other denominator prop names a discrete
count (`total` on IconArray, TallyMarks, Honeycomb…), but Progress tracks a continuous goal that can be overshot, so it
keeps the word for a ceiling.
## Four homes [#four-homes]
**In a sentence**
```tsx
Database migration to the new cluster is{" "}
{" "}
— on track for Friday's cutover.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
```
**In a tab header**
```tsx
Setup
```
## Accessibility [#accessibility]
The accessible name is the completion — **"68% complete."**, or **"3 of 5 steps."** when segmented, or **"32%
remaining."** with `positive="down"`. The interactive entry re-announces through a polite live region, throttled to
whole-percent changes so a streaming value doesn't spam screen readers, and the fill-width transition is gated on
reduced motion.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | The progressed amount. |
| `max` | `number` | Denominator (default 1). |
| `segments` | `number` | Discrete-chunk track — the chart says step count, not ratio. |
| `label` | `"percent" \| "value" \| "fraction" \| "none"` | The direct label; percent is the default datum. |
| `positive` | `"up" \| "down"` | down = burn-down wording (summary only; the bar stays factual). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# QuadrantDot (/docs/charts/quadrant-dot)
QuadrantDot places a focal dot by its 2-D position, marks the split with a hairline cross, and draws the peers as tiny
muted ghosts. The split defaults to the domain midpoints and takes an override, but it is never hidden: it renders
wherever the split truly is. The first read is which quadrant the item sits in against the field; exact position is the
second read, which is what keeps the chart legible at 24×24. At that size the axes carry no labels, so pass `xLabel`,
`yLabel`, and a `title` — the accessible name is doing the naming.
```tsx
```
## Install [#install]
```tsx
import { QuadrantDot } from "@microcharts/react/quadrant-dot";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { QuadrantDot } from "@microcharts/react/quadrant-dot/interactive";
```
## When to use it [#when-to-use-it]
Use it for the classic prioritization 2×2 (one cell per initiative in a table), an effort-vs-impact read in a KPI card,
and any "which quadrant, against the field" decision. For a full scatter plot use MicroScatter; for a single ranked list
use MiniBar.
## Sizing [#sizing]
**lone glyph (no field)**
```tsx
```
**named quadrants**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
```tsx
`">
```
When every field value on one axis equals the focal's, that axis's domain collapses to a point. The split line on that
axis is dropped rather than drawn at an arbitrary spot, while the other axis's split still renders. A peer exactly on
top of the focal is still counted (distance 0) and sorts first in the interactive entry's nearest-first order. A
non-finite focal position draws no marks at all: no cross, no tint, no peers. It carries the "No data." summary, the
same degenerate path an empty series takes elsewhere in the library.
## Four homes [#four-homes]
**In a sentence**
```tsx
Effort vs impact{" "}
{" "}
— one high-impact, low-effort outlier.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
1
initiative flagged
```
**In a tab header**
```tsx
Auth
```
Best at KPI/card scale — quadrants need a near-square seat.
## Accessibility [#accessibility]
The accessible name states the focal position and the axis-relative quadrant: **"Impact 9, effort 3 — in the
high-impact, low-effort quadrant."** With a `field` it adds how crowded that quadrant is, and
`quadrants={["quick win", …]}` replaces the generated quadrant names with your own. The interactive entry starts on the
focal, then cycles the peers nearest-first, announcing each one's coordinates and quadrant.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ x; y }` | The focal item's 2-D position. |
| `field` | `{ x; y }[]` | The peer set — omit for a lone glyph. |
| `xDomain` | `[number, number]` | The x-axis range (default: derived from the data); domain stays the y-axis. |
| `domain` | `[number, number]` | The y-axis range (default: derived from the data). |
| `split` | `[number, number]` | The quadrant boundary (default domain midpoints) — never hidden. |
| `quadrants` | `[TL, TR, BL, BR]` | Names in reading order — summaries only, never rendered. |
| `xLabel / yLabel` | `string` | Axis nouns for the summary — pass them, the axes are unlabeled at glyph size. |
| `region` | `boolean` | Faint tint on the focal's quadrant (default true; false for dense grids). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# QuantileDots (/docs/charts/quantile-dots)
QuantileDots draws a sample or a posterior as 20 dots, each one an equal-probability quantile. A dot is therefore about
a **1-in-N chance**, not a raw observation. Add a `threshold` and the dots past the line are re-inked and ringed, and
the summary reports the answer as a frequency ("10 in 20") rather than a percentage. Two cues carry the count, so it
survives grayscale, print, and forced-colors.
```tsx
```
## Install [#install]
```tsx
import { QuantileDots } from "@microcharts/react/quantile-dots";
`${n} min`} title="Bus wait" />
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { QuantileDots } from "@microcharts/react/quantile-dots/interactive";
```
## When to use it [#when-to-use-it]
Use it for a "will we miss the SLA?" read in a sentence, countable odds in a KPI card, and a posterior communicated as a
frequency rather than a percent. For a precise distribution shape use HistogramStrip; for one estimate's interval use
GradedBand.
## Sizing [#sizing]
**count the odds past a line**
```tsx
```
**fewer dots — faster to count**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
`format` also takes `Intl.NumberFormatOptions` — with a `locale`, the announced threshold follows that locale's own
grouping and decimal marks.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
Empty data draws nothing and the summary says so. An all-equal sample collapses every dot into one column: that is
certainty in the sample, not a rendering glitch. A threshold outside the observed range reads as 0 in N (or N in N), so
the count always reports the fraction of the field it catches.
Use `count` to change how many dots the sample is quantized into; 20 is the default because a reader subitizes a handful
of highlighted dots faster than they parse "48%".
## Four homes [#four-homes]
**In a sentence**
```tsx
Bus wait times{" "}
{" "}
— 16% of waits exceed the 15 min SLA.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
16%
over 15 min
```
**In a tab header**
```tsx
Route 42
```
## Accessibility [#accessibility]
The accessible name states the odds as a frequency: **"8 in 15 chances above 15 min."** Without a `threshold` it reads
"Most likely …; range …" instead. In the interactive entry, hovering or arrowing moves a live threshold and the count
past it recomputes as you go.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Raw sample or posterior draws — the component derives the quantile dots. |
| `count` | `number` | Number of quantile dots (default 20; 15–20 recommended; capped at 25). |
| `threshold` | `number` | The decision line — turns the plot from shape into odds. |
| `side` | `"above" \| "below"` | Which side of the threshold is the event being counted. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# QueueDepth (/docs/charts/queue-depth)
A support queue sits at 214 against a capacity of 100, and the question is whether it is draining or growing. QueueDepth
draws the queue **depth** as a zero-anchored area — a stock, not a rate — against a dashed **capacity** hairline.
Wherever the depth runs above capacity, the top edge is re-stroked in the negative ink, so a breach changes the *shape*
of the edge as well as its color and survives greyscale. The endpoint carries the current value and a trend glyph (▴/▾)
fit over the last quarter of the window: a low-precision direction cue, which is why the summary names that window in
words rather than projecting a finish.
```tsx
```
## Install [#install]
```tsx
import { QueueDepth } from "@microcharts/react/queue-depth";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { QueueDepth } from "@microcharts/react/queue-depth/interactive";
```
## When to use it [#when-to-use-it]
Use it for a support-queue backlog in a KPI card, a work-in-progress stock against its WIP limit, and a will-it-drain
read in a tab header. For a rate rather than a stock use Sparkline; for a single count use Delta.
## Sizing [#sizing]
**draining below capacity**
```tsx
```
**no capacity reference**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
With a `locale`, both the endpoint label and the accessible summary's numbers follow that locale's own grouping. In
German the summary reads **"2.140 queued, 2,1× capacity, growing over the last quarter."** rather than "2,140 queued,
2.1× capacity".
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
```tsx
`"
>
```
With no `capacity`, the area still draws and the summary reads **"214 queued, growing over the last quarter."** The
capacity clause drops, because there is nothing to compare against. A queue that stays under capacity reports **"30
queued, within capacity, draining over the last quarter."** and never re-strokes the edge. Gaps
(`null`/`NaN`/`±Infinity`) split the area into separate runs rather than drawing a line across missing periods.
## Four homes [#four-homes]
**In a sentence**
```tsx
Support queue backlog{" "}
{" "}
— 64 open, approaching capacity.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
64
open tickets
```
**In a tab header**
```tsx
Tier 1
```
## Accessibility [#accessibility]
The accessible name states the current depth, the capacity relationship, and the trend: **"214 queued, 2.1× capacity,
growing over the last quarter."** When the depth stays under capacity the middle clause becomes "within capacity"; with
no `capacity` it drops. The interactive entry steps the periods, announcing each depth and whether it is above capacity
(**"t8: 214 queued, above capacity."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Backlog depth per period (≥ 0). null / NaN / ±Infinity are gaps. |
| `capacity` | `number` | Steady-state capacity: a dashed hairline; spans above it re-stroke negative. |
| `label` | `"last" \| "none"` | Endpoint value + trend glyph (▴/▾), default 'last', or nothing. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# RateVolume (/docs/charts/rate-volume)
RateVolume draws a precise rate line over low-precision **ghost volume bars**, which are the denominator. No prop
removes the bars: a 4.1% conversion rate reads very differently on 38 events than on 3,800. The bars are muted and
unlabeled because the question they answer is "enough?", not "how many?". Set `minVolume` and any period below it
renders its rate point hollow, a shape cue that survives forced-colors and print.
```tsx
```
## Install [#install]
```tsx
import { RateVolume } from "@microcharts/react/rate-volume";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { RateVolume } from "@microcharts/react/rate-volume/interactive";
```
## When to use it [#when-to-use-it]
Use it for a conversion or error rate with its denominator, a KPI card where the rate is the headline, and spotting a
big rate move on thin volume. When volume itself needs a precise read, pair a SparkBar; for a plain series use
Sparkline.
## Sizing [#sizing]
**flag thin denominators**
```tsx
// a 4.1% rate on 38 events renders hollow — read it with caution
const periods = [
{ rate: 0.023, volume: 220 }, { rate: 0.025, volume: 190 }, { rate: 0.028, volume: 160 },
{ rate: 0.029, volume: 130 }, { rate: 0.031, volume: 110 }, { rate: 0.034, volume: 90 },
{ rate: 0.036, volume: 66 }, { rate: 0.041, volume: 38 },
];
```
**per-period step rates**
```tsx
const periods = [
{ rate: 0.023, volume: 220 }, { rate: 0.025, volume: 190 }, { rate: 0.028, volume: 160 },
{ rate: 0.029, volume: 130 }, { rate: 0.031, volume: 110 }, { rate: 0.034, volume: 90 },
{ rate: 0.036, volume: 66 }, { rate: 0.041, volume: 38 },
];
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
`format` and `volumeFormat` both take `Intl.NumberFormatOptions` and share the same `locale`. Under `de-DE` the rate
label reads "4,1 %" and volumes group with a period ("3.800"), following that locale's own conventions rather than a
hardcoded English format.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
A period with zero (or non-finite) volume renders no bar and no rate mark. A rate on **zero** volume is undefined
regardless of the input, so the line gaps across that period instead of interpolating through one nothing was measured
on, and the interactive entry announces it as **"no events"** rather than a rate. With no data at all, the chart renders
an empty root and the accessible name says **"No data."**
## Four homes [#four-homes]
**In a sentence**
```tsx
Conversion rate{" "}
{" "}
— 4.2% on 1,240 sessions this week.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
4.2%
1,240 sessions
```
**In a tab header**
```tsx
Web
```
## Accessibility [#accessibility]
The accessible name always pairs the rate with its volume: **"4.1% on 38 events (low volume); up from 2.3% across 5
periods."** The interactive entry steps the periods, and its live region states the volume with every rate, announcing
"no events" for a zero-volume period.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ rate; volume }[]` | Periods, oldest first — each a rate and the volume it was measured on. |
| `minVolume` | `number` | Below it, the rate mark renders hollow — 'insufficient denominator'. |
| `curve` | `"linear" \| "step"` | Step suits per-period aggregate rates. |
| `volumeFormat` | `Intl.NumberFormatOptions \| (n) => string` | Volume has different units than rate; formatted separately. |
| `unit` | `string` | Noun for the volume unit in the summary (default 'events'). |
| `volumeDomain` | `[number, number]` | Volume (bar) domain — defaults to [0, max], zero-anchored. |
| `label` | `"last" \| "none"` | Endpoint rate in a right gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# RetentionCurve (/docs/charts/retention-curve)
A cohort has run twelve weeks and the curve looks like it is flattening. RetentionCurve draws it as a **step** line
(cohort periods are discrete) on a scale **locked to 0–100%**: the full range is the frame a share is read against, and
a truncated floor would exaggerate or hide the drop. When the curve flattens, a dotted plateau marker appears. A
`compare` series rides behind as a subordinate dashed ghost.
```tsx
`"
>
```
## Install [#install]
```tsx
import { RetentionCurve } from "@microcharts/react/retention-curve";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { RetentionCurve } from "@microcharts/react/retention-curve/interactive";
```
## When to use it [#when-to-use-it]
Use it for a cohort retention curve in a KPI card, your decay against an industry benchmark, and spotting whether
retention plateaus or keeps leaking. For a continuous signal use Sparkline; for one-number retention use Progress or
Delta.
## Sizing [#sizing]
**vs industry benchmark**
```tsx
```
**smooth (editorial)**
```tsx
// step is the honest default for cohort data
```
## Variants [#variants]
```tsx
`"
>
```
The compare series stays a dashed ghost behind your curve rather than a second competing line. `curve="smooth"`
interpolates between periods, which implies values that were never measured; the step default doesn't. The plateau
marker appears only when the mean period-over-period change across the tail falls below half a point, never as
decoration.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
```tsx
`"
>
```
A single period is too short for plateau detection (the window needs at least four finite points) and renders as a
single step with no visible line. `data` accepts either a 0–1 fraction or a 0–100 percent series, whichever the max
value implies, so a raw percent export renders identically to its fraction form without a manual divide. With a
`locale`, the percent label and every announced number follow that locale's own formatting.
## Four homes [#four-homes]
**In a sentence**
```tsx
W12 cohort retention{" "}
{" "}
— 38% at week 12, above benchmark.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
38%
retained
```
**In a tab header**
```tsx
Jan
```
## Accessibility [#accessibility]
The accessible name states the final retention and, when one is detected, the plateau: **"38% retained after 10 weeks;
curve plateaus from week 6."** The interactive entry steps the periods and announces each period's retention alongside
the benchmark's.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Fraction retained per period (0–1 or 0–100); period 0 is typically 1.0. |
| `compare` | `number[]` | Peer/industry curve, drawn as a subordinate dashed ghost. The catalog word for a second series to read the first against (DualSparkline, StarSpoke). |
| `benchmark` | `number[]` | Deprecated alias for `compare`, still accepted and still winning when both are passed. |
| `plateau` | `boolean` | Detect + mark a plateau (default true). |
| `curve` | `"step" \| "smooth"` | Step (default — cohorts are discrete) or smooth (editorial). |
| `unit` | `string` | Period noun for the summary (default 'period'). |
| `label` | `"last" \| "none"` | Final retention in a right gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# RubricStrip (/docs/charts/rubric-strip)
RubricStrip encodes each criterion twice: bar length is the score, bar thickness is that criterion's share of the total
weight. A heavy criterion scoring poorly therefore carries more ink than a light one scoring well, which a table of the
same numbers hides. There is no total bar and none can be added; the summary names the highest and lowest criterion
instead of a weighted average, because one number throws away the structure. A `target` draws a single pass line across
every row.
```tsx
`"
>
```
## Install [#install]
```tsx
import { RubricStrip } from "@microcharts/react/rubric-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { RubricStrip } from "@microcharts/react/rubric-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for model and code-review scorecards and for weighted vendor comparison. For a single criterion use Bullet; for
parts of a whole use SegmentedBar.
## Sizing [#sizing]
**table cell**
```tsx
```
**with target**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
A score outside the domain clamps to a full-length bar rather than overflowing the track, and the component logs a dev
warning. Omit `weight` and every criterion carries the same thickness, which is what the unweighted variant above shows.
## Four homes [#four-homes]
**In a sentence**
```tsx
Model eval scorecard{" "}
{" "}
— 78% weighted, accuracy weakest.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
78%
weighted score
```
**In a tab header**
```tsx
GPT-4
```
## Accessibility [#accessibility]
The accessible name reports the count and the extremes: **"4 criteria; highest Lint (1), lowest Docs (0.5)."** The
interactive entry roves the criteria, announcing each one's score and its weight share of the total (**"Coverage: 0.78,
weight 29% of total."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label, score, weight? }[]` | Criteria; weights default equal. |
| `target` | `number` | Pass target — one tick across all rows. |
| `labels` | `boolean` | Criterion names in the left gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# RugStrip (/docs/charts/rug-strip)
RugStrip puts every value in a sample on one line as its own tick, so the full spread and the places the values crowd
read straight off the marks. Every tick is one real observation: no binning, no jitter, no thinning. Coincident values
darken through three opacity tiers (35%, 60%, 85%), so a lone tick stays visible while a stack reads as density; the
tiers are bucketed because a browser paints one path's stroke as a single operation, so the overlap compositing you
might expect never happens. `markValue` adds a full-height highlighted tick for one raw value against the field, which
is the strongest single read the strip offers; the regular ticks are inset by one unit so that highlight reads taller
without escaping the box.
```tsx
`"
>
{"Your offer sits "}
{" inside the band."}
```
## Install [#install]
```tsx
import { RugStrip } from "@microcharts/react/rug-strip";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { RugStrip } from "@microcharts/react/rug-strip/interactive";
```
## When to use it [#when-to-use-it]
Use it for a "you are here" read in a pay band, raw spread beside a stat, or margin composition under a Sparkline. Past
about 400 observations, reach for HistogramStrip: a rug promises raw marks and never downsamples. For trends over time
use Sparkline.
## Sizing [#sizing]
**you vs the field**
```tsx
```
**fixed domain across rows**
```tsx
// same scale per row or the rugs lie
v * 1.9)} domain={[0, 200]} />
```
## Variants [#variants]
```tsx
`">
```
```tsx
`"
>
```
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
Empty data draws a centered axis line rather than a blank hole, so the strip still reads as a rug with nothing on it
instead of a layout gap.
```tsx
`">
```
A single value (or an all-equal field) has a zero-span domain; the tick renders at the strip's midpoint instead of
dividing by zero.
```tsx
`">
```
A `markValue` outside the observed domain clamps to the nearest edge. It never escapes the box, and it never rescales
the field around itself.
## Four homes [#four-homes]
**In a sentence**
```tsx
Their $62k offer sits{" "}
{" "}
inside the band, not at either edge.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
$62k
inside the range
```
**In a tab header**
```tsx
Engineering
```
## Accessibility [#accessibility]
The accessible name is the distribution: **"8 values from 42 to 75, median 59.5."** The interactive entry walks the
observations in sorted order and announces each one with its rank: **"48 — 2nd of 17."**
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Raw observations — position = value. |
| `markValue` | `number` | One value emphasized against the field. |
| `orientation` | `"horizontal" \| "vertical"` | Vertical rugs sit beside distributions. |
| `domain` | `[number, number]` | Fix the scale across rows (rugs mislead worst under per-row autoscale). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# SegmentedBar (/docs/charts/segmented-bar)
SegmentedBar splits one bar into adjacent segments, one per labeled category, that always sum to the whole: the read is
what a thing is made of, and in what proportions. Segments keep your data order by default, because sequences like
funnel stages and weekday mixes carry meaning that ranking destroys; `order="desc"` ranks them when they have none. Past
`maxSegments` the tail rolls into a labeled "Other" rather than being dropped, and summary percents use
largest-remainder rounding so they sum to exactly 100.
```tsx
```
## Install [#install]
```tsx
import { SegmentedBar } from "@microcharts/react/segmented-bar";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { SegmentedBar } from "@microcharts/react/segmented-bar/interactive";
```
## When to use it [#when-to-use-it]
Use it for a traffic mix per table row or composition in a KPI card. For precise cross-row comparison use MiniBar; for
negative parts use Waterfall. This is also the replacement for the pie chart the catalog doesn't ship — see
[Design notes](/docs/design-notes).
## Sizing [#sizing]
**table cell**
```tsx
```
**value labels**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`"
>
```
`label="value"` follows `format`/`locale` — 12400 reads "12.400" under `de-DE` rather than the English "12,400".
`label="percent"` is unaffected: the largest-remainder shares are always plain integers, never locale-formatted, because
a percent point is the same everywhere.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
Past `maxSegments` (default 5) the smallest categories merge into a labeled "Other". Nothing is dropped, only rolled up,
and the interactive entry announces its member count. A single category still renders as one full-width, 100% segment
rather than a special-cased bare bar. A part-to-whole cannot contain negative parts, so negative values are excluded
from the bar entirely (not clamped to zero) and the component logs a dev warning steering toward Waterfall.
## Four homes [#four-homes]
**In a sentence**
```tsx
This week's sessions skew Chrome{" "}
{" "}
— 62% Chrome, 24% Safari, the rest long tail.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
62%
sessions on Chrome
```
**In a tab header**
```tsx
Marketing
```
## Accessibility [#accessibility]
The accessible name is the full composition: **"Chrome 62%, Safari 24%, Firefox 9%, Edge 3%, Other 2%."** The
interactive entry roves the segments, announcing each one's share and value. The Other segment adds the total it stands
for and how many categories were folded into it (**"Other: 2%, 20 over 2 categories."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; value }[]` | Parts of the whole. |
| `maxSegments` | `number` | Rollup threshold — the tail becomes a labeled Other. |
| `order` | `"data" \| "desc"` | Preserve inherent sequences or rank the composition. |
| `label` | `"none" \| "percent" \| "value"` | Centered per segment (deterministic drop-out; default percent). |
| `colors` | `string[]` | Per-segment colours, cycled; overrides --mc-cat-N. Other stays neutral. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Seismogram (/docs/charts/seismogram)
Errors arrive in bursts, and the question is when they hit and how hard. Seismogram centers its ticks on a midline and
flares them symmetrically: density is presence, length is intensity. The centered trace keeps the strip from inviting
the bar-to-bar magnitude comparison it is too small to support, and keeps it visually distinct from SparkBar. Long
series collapse via max-per-bucket only, so a spike always survives, and the summary is computed from the raw values
rather than the buckets.
```tsx
`"
>
```
## Install [#install]
```tsx
import { Seismogram } from "@microcharts/react/seismogram";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Seismogram } from "@microcharts/react/seismogram/interactive";
```
## When to use it [#when-to-use-it]
Use it for error bursts per service, alert density, and activity texture in table rows. For level tracking use
Sparkline; for labeled events use EventTimeline.
## Sizing [#sizing]
**table row**
```tsx
```
**barcode (presence only)**
```tsx
// heights are noise? declare it — uniform ticks say "when", not "how hard"
```
**flag anomalies**
```tsx
// spikes at or above the threshold flare in the alert token
```
## Variants [#variants]
```tsx
`"
>
```
In `barcode` mode every tick is the same length: it records presence and carries no magnitude.
```tsx
`"
>
```
```tsx
`"
>
```
`anomaly` flags spikes at or above its threshold in the alert token, on top of the tick length that already shows them,
so the flag never rests on color alone.
```tsx
`"
>
```
`format` also takes `Intl.NumberFormatOptions` — with a `locale`, the accessible summary's peak number follows that
locale's own grouping ("12.500" in German, not "12,500"). Tick positions and lengths are unaffected; only the announced
peak is localized.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
(i === 210 ? 9 : i % 17 === 0 ? 2 : 0))}
/>`"
>
```
All-zero data renders the baseline at rest rather than a blank hole. A series longer than the pixel width collapses by
max-per-bucket, so a single sharp spike is never averaged away.
## Four homes [#four-homes]
**In a sentence**
```tsx
Checkout errors this hour{" "}
{" "}
— 29 events, spiking to 11 at minute 28.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
29
events/hr · peak 11
```
**In a tab header**
```tsx
checkout-api
```
## Accessibility [#accessibility]
The accessible name counts the events: **"29 events, peak 11."**, or **"No events."** for a quiet strip. The interactive
entry steps slots with position readouts ("Point 2 of 5: 3."). A quiet slot reads as the zero it is ("Point 1 of 5:
0."), and only a missing sample announces "no data". Home and End jump to the first and last event.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `(number \| null)[]` | Per-slot event intensity; 0/null = quiet. |
| `mode` | `"intensity" \| "barcode"` | Barcode collapses heights — pure occurrence density. |
| `positive` | `"up" \| "down"` | Polarity coloring of signed ticks. |
| `anomaly` | `number` | Flag spikes: \|v\| ≥ threshold flares in the alert token. |
| `domain` | `[number, number]` | Fixed intensity scale across rows. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# ShiftHistogram (/docs/charts/shift-histogram)
ShiftHistogram mirrors two histograms around a center line: **before** upward (muted), **after** downward (accent), on
**shared bin edges**. Bar heights are per-side proportions (each side's counts over its own n) on one shared scale, so a
bigger sample on one side cannot fake a shift. The mirror carries **identity**, not valence — up is "before", not
"good". The median shift is the precise takeaway, and the medians are never smoothed or trimmed.
```tsx
```
## Install [#install]
```tsx
import { ShiftHistogram } from "@microcharts/react/shift-histogram";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { ShiftHistogram } from "@microcharts/react/shift-histogram/interactive";
```
## When to use it [#when-to-use-it]
Use it for a "the fix, proven" read in a KPI card, before/after distributions in an experiments table, and showing that
a change is more than a mean move. For a single distribution use HistogramStrip; for two labelled arms use ABStrips.
## Sizing [#sizing]
**overlay for similar shapes**
```tsx
const before = Array.from({ length: 100 }, (_, i) => 120 + (i % 40) - 20);
const after = Array.from({ length: 100 }, (_, i) => 96 + (i % 40) - 20);
```
**no real shift**
```tsx
const before = Array.from({ length: 100 }, (_, i) => 120 + (i % 40) - 20);
```
## Variants [#variants]
```tsx
120 + (i % 40) - 20);
const after = Array.from({ length: 100 }, (_, i) => 96 + (i % 40) - 20);
`"
>
```
```tsx
`"
>
```
The median shift label and accessible summary follow `format`/`locale`. Under `de-DE` the before median above reads
"12.400" rather than the English "12,400". `mode="overlay"` draws the after bins above the axis as an outline over the
before fill instead of mirroring them below it; the bins stay shared either way.
## Edge cases [#edge-cases]
```tsx
120 + (i % 40) - 20);
Math.round(n) + " ms"}
title="Awaiting after sample"
/>`"
>
```
```tsx
`">
```
With one side empty the chart still renders the populated side's bins alone, and the summary names the gap: **"Median
116 ms; no after sample."** It never reports a fabricated shift. With both sides empty nothing is plottable and the
accessible name says **"No data."**
## Four homes [#four-homes]
**In a sentence**
```tsx
The fix, proven{" "}
{" "}
— latency distribution shifted left after deploy.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
−20ms
after fix
```
**In a tab header**
```tsx
checkout
```
## Accessibility [#accessibility]
Side identity rides on position (up is before, down is after), so it survives grayscale and forced-colors. The
accessible name states the median shift: **"Median fell from 116 ms to 92 ms."** It appends "On N / M samples" when the
sample sizes differ. The interactive entry steps the bins and announces each bin's before/after proportions; **M** jumps
to the two median bins.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ before: number[]; after: number[] }` | The two samples — raw observations, shared bin edges are derived. |
| `bins` | `number` | Shared bin count (default auto, Sturges capped at 12). |
| `mode` | `"mirror" \| "overlay"` | Mirror (default) or after-as-outline over before fill. |
| `seriesLabels` | `[string, string]` | Side identities for the summary (default ['before', 'after']). |
| `label` | `"shift" \| "none"` | Signed median shift in a right gutter. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Slope (/docs/charts/slope)
Slope draws one line per category between two moments: East 40 to 47, West 55 to 41, and any crossing between them. Both
columns share one y-domain, since per-column normalization would fake convergence. Lines stay neutral until you declare
`positive`, because a rank change is not automatically good or bad.
```tsx
`"
>
```
## Install [#install]
```tsx
import { Slope } from "@microcharts/react/slope";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Slope } from "@microcharts/react/slope/interactive";
```
## When to use it [#when-to-use-it]
Use it for before/after experiments, rank shuffles, and two-moment comparisons, up to about 7 categories. A two-point
line says nothing about the path between the moments, so use Sparkline when that path is the point.
## Sizing [#sizing]
**KPI before/after**
```tsx
```
**one vs the field**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
End labels drop deterministically rather than by measurement: when the rows are denser than the label font (height ÷
count), and when the reserved label gutters would squeeze the two columns under \~35% of the width. The reclaimed room
goes back to the lines. Endpoints that would collide inside a column are nudged apart to a full glyph pitch instead of
overlapping.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
```tsx
`"
>
```
A row missing one side (`from` or `to` as `NaN`) draws a short dashed stub toward the end it does have rather than a
full line. There is no second point to connect to, so nothing is interpolated, and the row is announced as "incomplete".
With a `locale`, both column labels and the announced values follow that locale's own grouping.
## Four homes [#four-homes]
**In a sentence**
```tsx
West's renewal rate slid from best to worst region{" "}
{" "}
— down 25%.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
20% → 35%
Mid region, up 75%
```
**In a tab header**
```tsx
Regions
```
## Accessibility [#accessibility]
The accessible name counts directions and leads with the biggest mover: **"3 categories: 2 up, 1 down. Largest change
Mid, up 75%."** The interactive entry finds the nearest line under the pointer and roves categories ordered by their
after-value, announcing each slope (**"East: 40 to 47, up 18%."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; from; to }[]` | Two aligned moments per category. |
| `label` | `"none" \| "value" \| "label" \| "both"` | End labels; dropped deterministically when rows collide. |
| `highlight` | `number \| string` | The one-vs-field editorial read. |
| `positive` | `"up" \| "down"` | Direction valence; unset = neutral ink. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# SparkBar (/docs/charts/sparkbar)
SparkBar renders discrete values as compact bars from a zero baseline: one bar per period, always anchored at zero,
because a bar that starts anywhere else misstates its magnitude. Each period is its own count — a deploy total, a daily
total — rather than a sample on a continuous curve. In `bar` mode the endpoint bar takes the accent, since "where did it
land" is usually the first question a run of counts answers.
`winloss` mode discards magnitude on purpose and collapses each value to its sign: wins above the mid-line, losses
below, and a tie (`0`) as a thin neutral dash on it. There every bar keeps its own win/loss/tie color, endpoint
included, because the sign is what matters at each position. Sign is doubled by position and color wherever signed data
appears, so direction survives forced-colors.
```tsx
```
## Install [#install]
```tsx
import { SparkBar } from "@microcharts/react/sparkbar";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { SparkBar } from "@microcharts/react/sparkbar/interactive";
```
## When to use it [#when-to-use-it]
Use it for discrete magnitudes, win–loss streaks, and period-over-period counts. For a continuous trend shape use
[Sparkline](/docs/charts/sparkline), which is also the better choice at hundreds of points, where bars become noise.
## Sizing [#sizing]
`width` and `height` are viewBox units that also set the rendered pixel box. Omit them and drive the width from CSS for
a chart that fills its container — the viewBox keeps the aspect ratio.
**default**
```tsx
// data alone → an intrinsic 80×20 box
```
**fixed size**
```tsx
// width & height are viewBox units — they also set the pixel box
```
**responsive**
```tsx
// omit width/height, let CSS drive the width — the viewBox keeps the ratio
```
## Variants [#variants]
```tsx
`">
```
```tsx
`">
```
```tsx
`"
>
```
```tsx
`">
```
```tsx
`">
```
The endpoint label and accessible summary both follow `format`/`locale`: 8600 reads "8.600" under `de-DE` rather than
the English "8,600".
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
An empty series renders no bars and an accessible name that says so, rather than a flat or invented baseline. A single
value still renders as one zero-anchored bar, and `winloss` mode treats it the same way a longer streak would: a lone
win, loss, or tie.
## Four homes [#four-homes]
**In a sentence**
```tsx
Deploys peaked at nine mid-week{" "}
{" "}
— quieter since.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
5 / 7
last 7 runs
```
**In a tab header**
```tsx
CI
Release
```
## Accessibility [#accessibility]
The accessible name is generated from the data. The example above reads:
> Deploys per day. Trending up 125%. Range 2 to 9. Last value 9.
Sign is doubled by position (above or below the baseline), never by color alone, so the chart survives forced-colors and
color-blind viewing.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Values; negatives dip below the baseline. |
| `mode` | `"bar" \| "winloss"` | Magnitude bars, or a win/loss/tie streak (sign only). |
| `gap` | `number` | Empty fraction of each slot (0–0.9). |
| `label` | `"none" \| "last"` | Direct endpoint value label. |
| `positive` | `"up" \| "down"` | "up" (default); "down" flips which sign is good. |
| `title` | `string` | Accessible name; joins the auto summary. |
| `summary` | `string \| false` | Override or disable the auto summary. |
| `locale` | `string \| string[]` | BCP 47 locale(s) for the endpoint label and summary. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Sparkline (/docs/charts/sparkline)
A quarter of revenue is six numbers in a row, and what you want from them is the shape. Sparkline draws those ordered
values as one line, sized for a sentence, a table cell, or a KPI card. The default is the line plus an accent dot on the
last point, which marks where "now" is; fill, a normal-range band, and min/max dots are props you turn on.
```tsx
```
## Install [#install]
```tsx
import { Sparkline } from "@microcharts/react/sparkline";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Sparkline } from "@microcharts/react/sparkline/interactive";
```
## When to use it [#when-to-use-it]
Use it for an inline trend, a table-cell trend, a KPI sparkline, and dense dashboards. It won't answer part-to-whole
comparisons or exact category values: use [SparkBar](/docs/charts/sparkbar) for discrete magnitudes, or
[Bullet](/docs/charts/bullet) for a value against a target.
## Sizing [#sizing]
`width` and `height` are viewBox units that also set the rendered pixel box. Omit them and drive the width from CSS for
a chart that fills its container — the viewBox keeps the aspect ratio.
**default**
```tsx
// data alone → an intrinsic 80×20 box
```
**fixed size**
```tsx
// width & height are viewBox units — they also set the pixel box
```
**responsive**
```tsx
// omit width/height, let CSS drive the width — the viewBox keeps the ratio
```
## Variants [#variants]
Shape, fill, a normal-range band, and dots are props on the same data. Each is off by default: at word size an extra
fill or band takes pixels away from the line itself. Open the Code tab on any demo to see the call.
```tsx
`">
```
`curve="smooth"` interpolates with monotone cubic tangents, so the curve stays inside the range its own points span. A
spike back down to zero cannot bow below zero on the way, and a filled area cannot cross its baseline.
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
```tsx
`">
```
A `null` means "no measurement here": the line breaks at the gap and never interpolates across it, so an outage doesn't
render as a smooth trend. A single point has no line to draw; it sits centered in the plot, visible as the default
endpoint dot. An all-equal series renders on the vertical mid-line, because a zero-span domain maps to the middle of the
range, so flat data reads as level rather than bottomed-out.
Past `maxPoints` (default 200) the drawn line decimates to a min/max-preserving envelope: every spike keeps its true
position and height, gaps stay gaps, and the summary, dots, and hover values still come from the raw data. Only the path
gets lighter. An envelope is used instead of a stride sample so that a single spike at point 1,500 of 2,000 still
renders. Pass `maxPoints={Infinity}` to opt out.
```tsx
i === 1500 ? 98 : 50 + Math.sin(i / 40) * 24 + ((i * 13) % 7),
)}
/>`"
>
```
## Four homes [#four-homes]
The same chart, sized for four real contexts. Each preview is the public component plus `styles.css` — no docs-only
wrappers. Sentence placements use `mc-inline`; see [Composition](/docs/composition) and [Sizing](/docs/sizing).
**In a sentence**
```tsx
p95 latency this week{" "}
{" "}
— trending down.
```
**In a table cell**
```tsx
checkout-api
31 ms
```
**In a KPI card**
```tsx
1,600
concurrent, now
```
**In a tab header**
```tsx
CPU
```
## Accessibility [#accessibility]
By default the chart is an `img` whose accessible name is generated from the data. The example above is announced as:
> Weekly revenue. Trending up 200%. Range 3 to 9. Last value 9.
Pass a `title` to name it, or `summary={false}` with no `title` to make it decorative when the surrounding text already
says what it shows. The interactive entry adds a polite live region that reads each focused point as you arrow through
it.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | The series. null/NaN are gaps. |
| `curve` | `"linear" \| "smooth" \| "step"` | Line shape. |
| `fill` | `boolean` | Zero-anchored area under the line. |
| `band` | `[number, number]` | Constant normal-range band. |
| `dots` | `"auto" \| "minmax" \| "none"` | Endpoint or min/max dots. |
| `label` | `"none" \| "last" \| "minmax"` | Direct value labels: endpoint, or the extremes. |
| `maxPoints` | `number` | Line-point cap (default 200); longer series decimate min/max-preserving. |
| `title` | `string` | Accessible name; joins the auto summary. |
| `summary` | `string \| false` | Override or disable the auto summary. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# SpiralYear (/docs/charts/spiral-year)
SpiralYear winds a calendar series onto an Archimedean spiral. The angle is the position in the year (January at 12
o'clock, running clockwise) and each turn outward is the next year, so the radius encodes time only, never value: an
outer mark is a later date, not a bigger number. The value becomes a five-step opacity, and busy stretches render as
darker arcs. Two adjacent turns place different years at the same angle; read that radial adjacency as calendar
alignment. This is a **pattern instrument**: opacity is the weakest ordered channel, so for an exact day's value reach
for `ActivityGrid` or `HeatStrip`.
```tsx
```
## Install [#install]
```tsx
import { SpiralYear } from "@microcharts/react/spiral-year";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { SpiralYear } from "@microcharts/react/spiral-year/interactive";
```
## When to use it [#when-to-use-it]
Use it for the seasonal shape of a year, for spotting a busy season or a quiet stretch, or as a compact "the year in one
square". For an exact day's value use `ActivityGrid` or `HeatStrip`, and for a non-cyclic trend use `Sparkline`. Past
about three years the turns pack too tightly to read.
## Sizing [#sizing]
**arc marks for a continuous-ribbon feel**
```tsx
```
**three steps for the smallest sizes**
```tsx
```
## Variants [#variants]
Month ticks are on by default: without a temporal anchor the spiral is texture, and the ticks are what make "when"
readable. Five opacity steps is the ceiling, since ordered opacity supports about five discriminable levels; `steps={3}`
drops to three for tiny sizes.
```tsx
{
const s = Math.round(200 + 140 * Math.sin(((i - 8) / 52) * Math.PI * 2));
return i === 29 ? 480 : s; // a summer peak in week 30
});
`"
>
```
`locale` changes no in-chart mark, since opacity steps carry the value and no number is printed. It does localize the
peak and low values named in the accessible summary:
```tsx
{
const s = Math.round(200 + 140 * Math.sin(((i - 8) / 52) * Math.PI * 2));
return i === 29 ? 1480 : s;
});
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
A `null` day or week leaves a gap in the spiral, distinct from a faint step-one mark, because missing is not the same as
low.
## Four homes [#four-homes]
**In a sentence**
```tsx
Revenue seasonality{" "}
{" "}
— summer swell, outlier week 29.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
30
summer peak
```
**In a tab header**
```tsx
2024
```
Year spiral needs card scale — weekly rings collapse below ~36px.
## Accessibility [#accessibility]
The accessible name states the count, the peak, and the low: **"52 weeks; peak 1.480 in week 30, low in week 48."** (the
`de-DE` localized demo). The interactive entry lets you arrow along the spiral chronologically, and announces each mark
with its period and value through a polite live region.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `(number \| null)[]` | One value per day or week (cadence inferred from length). |
| `cadence` | `"day" \| "week"` | Data cadence; inferred from length (≈52 → week, else day) when omitted. |
| `startDate` | `string` | ISO date anchoring index 0 to a calendar angle. |
| `steps` | `3 \| 5` | Opacity quantization (default 5). |
| `monthTicks` | `boolean` | Faint radial month ticks (default true). |
| `mark` | `"dot" \| "arc"` | Dots (default) or short arc segments. |
| `size` | `number` | Spiral box edge in viewBox units (default 24). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# SpreadBand (/docs/charts/spread-band)
The gutter of the chart below reads **+8**: Organic is eight ahead of Paid, and it took that lead at point 3. SpreadBand
fills the signed gap between a subject and its reference and splits the fill at every crossing, so the current lead and
the last flip both render. The reference line is dashed, thinner, and neutral. Direction is carried by the sign of the
fill and by the text, never by color alone. Both series share one domain: no dual axes, no per-series normalization.
```tsx
```
## Install [#install]
```tsx
import { SpreadBand } from "@microcharts/react/spread-band";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { SpreadBand } from "@microcharts/react/spread-band/interactive";
```
## When to use it [#when-to-use-it]
Use it for lead-vs-reference in KPI cards, and for actual-vs-plan where the flip matters. Three or more series belong in
SparkGroup; unpaired series or different units belong in separate charts, never on dual axes.
## Sizing [#sizing]
**lead vs reference in a cell**
```tsx
{rows.map((r) => (
))}
```
**actual vs plan with the gap**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`"
>
```
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 [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`">
```
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 instead of inventing a value. Identical series read "The two series are level — no gap." A
subject that never falls behind reports "never crossed." The fill splits exactly at the interpolated crossings, so a
flip lands where the lines cross, even when that falls between two samples.
## Four homes [#four-homes]
**In a sentence**
```tsx
Organic vs paid{" "}
{" "}
— organic leads by 18% on median.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
+18%
organic vs paid
```
**In a tab header**
```tsx
Organic
```
## Accessibility [#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](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ 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` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# SproutRow (/docs/charts/sprout-row)
Six accounts sit in a portfolio table and each one is somewhere between just-signed and fully adopted. SproutRow gives
each item one of four growth stages (**seed → sprout → leaf → bloom**), and the glyph height is strictly monotonic, so
taller always means further along and the ordering reads without the key. The four stages are fixed and discrete: no
half-stages, no interpolation, and presets recolor the glyphs without reshaping one.
```tsx
```
The key, shown once: **seed → sprout → leaf → bloom**.
## Install [#install]
```tsx
import { SproutRow } from "@microcharts/react/sprout-row";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { SproutRow } from "@microcharts/react/sprout-row/interactive";
```
## When to use it [#when-to-use-it]
Use it for account or project maturity across a small set, a health column in a portfolio table, or per-item lifecycle
in a KPI card. Continuous values belong in MiniBar and trends in Sparkline; past about twelve items the row stops being
readable.
## Sizing [#sizing]
**with category labels**
```tsx
```
**missing ≠ seed (null draws a soil tick only)**
```tsx
```
## Variants [#variants]
Labels are off by default, since the row usually sits beside its own row label and the heights carry the ordering.
`label="value"` prints each item's stage number instead.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
Empty data draws just the frame. A `null` value renders only the soil tick, visibly distinct from a seed: seed is stage
0 and gets its own glyph, and "no data yet" is a different state from "just planted". Values outside `[0, 3]` round and
clamp to the nearest real stage, seed or bloom, instead of drawing a fractional or out-of-bounds glyph.
## Four homes [#four-homes]
**In a sentence**
```tsx
Account health this week{" "}
{" "}
— two at bloom, one still seed.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
4 / 6
at leaf or better
```
**In a tab header**
```tsx
Accounts
```
## Accessibility [#accessibility]
The accessible name summarizes the row: **"4 items; 1 at bloom, 1 at seed."** The interactive entry roves the items with
←/→ or hover and announces each as **"Acme: bloom, stage 4 of 4."**, the stage name plus a 1-of-4 index. A missing item
reads "…: no data." A ring lifts the focused glyph.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label, value }[]` | value = stage 0–3. |
| `labels` | `boolean` | Category labels under the slots. |
| `label` | `"none" \| "value"` | Print the stage number above each glyph. |
| `step` | `number` | Horizontal spacing between glyph slots (default 16; widens for labels). |
| `fontSize` | `number` | Type size of the value and stage labels, in viewBox units. Defaults from `height`. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# StackedArea (/docs/charts/stacked-area)
Traffic splits between mobile, web, and API, and the question is whether that mix is moving. StackedArea takes up to
three labeled series over the same ordered points and normalizes each to a share before stacking it, so layer thickness
is share and the stack always sums to 100%. That keeps the read on composition rather than on magnitude and composition
together, and the shift over time is what the shape shows. Three series is the hard cap: beyond that, thickness at 16 px
stops being readable.
```tsx
```
## Install [#install]
```tsx
import { StackedArea } from "@microcharts/react/stacked-area";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { StackedArea } from "@microcharts/react/stacked-area/interactive";
```
## When to use it [#when-to-use-it]
Use it for traffic or revenue mix in KPI cards and share-shift reads in a sentence. Four or more series need a different
chart; for exact values over time use a SparkGroup of Sparklines.
## Sizing [#sizing]
**mix rows**
```tsx
{regions.map((r) => (
))}
```
**ridge skin**
```tsx
```
## Variants [#variants]
`order="asc"` puts the smallest series on top, where curvature distorts thickness the least.
```tsx
`"
>
```
`format`/`locale` control the share numbers: the accessible summary and endpoint labels follow the locale's own percent
formatting ("66 %" in German, with a space before the sign, not "66%"). The stack itself never changes shape; only the
announced and printed numbers localize.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
A single column still stacks normally: share is well-defined from one point, so it renders as a vertical slice with no
width to run across. A series pinned at zero collapses to a zero-height layer instead of distorting the others, and the
remaining series still sum to 100% of what's left.
## Four homes [#four-homes]
**In a sentence**
```tsx
Traffic mix this year{" "}
{" "}
— mobile overtook web, now 66% of sessions.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
47%
subscriptions, down from 62%
```
**In a tab header**
```tsx
Traffic
```
## Accessibility [#accessibility]
The accessible name is the share-shift read: **"3 series over 12 points; Mobile leads at 66% share."** The interactive
entry announces every layer at once (**"Point 8 of 12: Mobile 56%, Web 36%, API 8%."**), and the readout chip shows that
same breakdown, one row per band.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; values }[]` | ≤ 3 series (hard cap). |
| `mode` | `"stacked" \| "ridge"` | Ridge = same stack, overlapping-crest skin. |
| `order` | `"data" \| "asc"` | "asc" puts the smallest series on top (least distortion). |
| `label` | `"last" \| "none"` | Endpoint share labels per series (deterministic drop-out). |
| `labelAt` | `number` | Column whose shares feed label="last" (default: final column). The interactive entry passes the focused column so the labels track the crosshair. |
| `curve` | `"linear" \| "smooth" \| "step"` | Line interpolation (default linear); ridge forces smooth. |
| `colors` | `string[]` | Per-series colours, cycled; overrides --mc-cat-N. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# StarSpoke (/docs/charts/star-spoke)
The chart below prints five spokes from one center, each spoke's length one metric: Speed at 0.9, Cost at 0.3. That is
an entity's profile, and a row of them shows which entity in a set is the odd one out. There is no connecting polygon,
ever: the enclosed area of a radar chart misstates magnitude and changes with axis order, and the research favors
contour-free marks for spotting outliers. One `domain` governs every spoke in a glyph, so normalizing mixed-unit metrics
is the caller's job and a set of small multiples stays comparable.
```tsx
```
## Install [#install]
```tsx
import { StarSpoke } from "@microcharts/react/star-spoke";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { StarSpoke } from "@microcharts/react/star-spoke/interactive";
```
## When to use it [#when-to-use-it]
Use it for entity profiles in small multiples and for skill or capability comparison. Fewer than 3 metrics read better
as PairedBars, and precise values belong in MiniBar.
## Sizing [#sizing]
**small multiple**
```tsx
```
**vs baseline**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
`locale` changes no in-chart mark, since tip labels are metric names rather than formatted numbers. It does localize the
extremes named in the accessible summary:
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`">
```
Every spoke sits at a fixed clock position, the first at 12 o'clock and the rest clockwise, so the axis order never
shifts between instances. That fixed order plus the default-on guide spokes keep a profile readable where a metric's
label doesn't seat. Tip labels seat at the rim rather than at the value tip, so a low-value spoke never drags its label
into the hub. A label is dropped only when its estimated width exceeds the whole glyph; anything narrower is clamped
into the reserved label ring, which keeps rim labels at distinct angles.
## Four homes [#four-homes]
**In a sentence**
```tsx
Product profile{" "}
{" "}
— strong on speed, weak on cost.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
0.9
top dimension
```
**In a tab header**
```tsx
Product A
```
Best at KPI/card scale — spoke tips need room to read.
## Accessibility [#accessibility]
The accessible name names the extremes: **"5 metrics; highest Speed (0.9), lowest Cost (0.3)."** The interactive entry
rotates focus through the spokes with ←/→, announcing each metric and value.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label, value }[]` | 3–8 metrics on a shared domain. |
| `dots` | `"tips" \| "none"` | `"tips"` draws endpoint dots to sharpen the outlier read. |
| `guides` | `boolean` | Full-length guide spokes (read-back scaffold). |
| `compare` | `number[]` | Muted ghost baseline spokes. |
| `labels` | `boolean` | Spoke labels at the tips (default true; drop out below size 44). |
| `size` | `number` | Star box edge in viewBox units (default 80). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# StationGlyph (/docs/charts/station-glyph)
A weather table has one row per station and five fields to fit in each cell. StationGlyph packs the whole observation
into one character, the way a meteorologist's station model does: the center disc fills with sky cover, a wind barb
gives direction and quantized speed, and up to three corner numerals carry temperature, dew point, and pressure. Each
field rides its own channel, so nothing has to be inferred from a shared one, and the numerals sit where a forecaster
expects them.
```tsx
```
## Install [#install]
```tsx
import { StationGlyph } from "@microcharts/react/station-glyph";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { StationGlyph } from "@microcharts/react/station-glyph/interactive";
```
## When to use it [#when-to-use-it]
Use it for a dense weather station model, or any multi-field reading that has to fit one cell. A single value belongs in
Delta and a trend over time in Sparkline.
## Sizing [#sizing]
**clear + calm**
```tsx
```
**overcast + gale**
```tsx
```
## Variants [#variants]
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
```tsx
`"
>
```
Sky cover is disc area and wind is the barb: direction as angle, speed quantized into barbs so the per-barb quantum
holds. Wind speed is quantized rather than drawn to scale for the same reason WindBarb quantizes it, that a barb count
reads exactly where a tiny length difference does not. Below the quarter-step threshold no barb is drawn at all. Absent
fields don't render: no barb, no numeral, and no stop for the keyboard. The disc is the one field with no absent state,
so omitting `cloud` looks and reads exactly like a clear sky.
`format`/`locale` flow through every corner numeral (temp, dew point, pressure) and through the wind speed and numerals
named in the accessible summary: one formatter, one locale, no per-field overrides.
## Four homes [#four-homes]
**In a sentence**
```tsx
KSFO conditions{" "}
{" "}
— overcast, SW 15 kt, 16°C.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
16°C
· 1013 hPa
```
**In a tab header**
```tsx
KSFO
```
## Accessibility [#accessibility]
The accessible name is the whole observation: **"KJFK, wind northwest 45; sky overcast, 4° / 2°, 988."** The interactive
entry roves the *present* fields with ←/→, so a screen-reader user can step through station, wind, sky, temperature, dew
point, and pressure one at a time; an omitted prop is not a stop. Escape clears the focus back to the whole-observation
reading.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `cloud` | `number` | Sky cover 0–1; fills the disc. |
| `wind` | `{ direction, magnitude }` | Barb direction + speed. |
| `step` | `number` | Wind-barb quantum — each full barb (default 10). |
| `temp` | `number` | Upper-left numeral. |
| `dewpoint` | `number` | Lower-left numeral. |
| `pressure` | `number` | Upper-right numeral. |
| `station` | `string` | Top-left identifier. |
| `size` | `number` | Disc + barb square edge in viewBox units (default 48). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# StatusDot (/docs/charts/status-dot)
StatusDot prints one mark for one state: a filled circle for `ok`, a triangle for `warn`, a diamond for `error`, a
hollow ring for `off`, a half-filled circle for `busy`. Every state pairs a distinct silhouette with a semantic color,
so the five stay distinct in grayscale, in print, and in forced-colors, where color-only dots all render as the same
gray circle. That pairing is also what keeps two states from collapsing into the same mark for the 1-in-12 colorblind
readers a colored disc fails.
```tsx
{"The API is "}
{" operational."}
```
## Install [#install]
```tsx
import { StatusDot } from "@microcharts/react/status-dot";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { StatusDot } from "@microcharts/react/status-dot/interactive";
```
## When to use it [#when-to-use-it]
Use it for service lists, inline state in a sentence, and monitoring rows. It carries no quantity and no trend, and past
\~6 states the marks stop being memorable.
## The state contract [#the-state-contract]
| status | glyph | token |
| ------- | ------------------ | --------------- |
| `ok` | filled circle | `--mc-positive` |
| `warn` | triangle | `--mc-cat-1` |
| `error` | diamond | `--mc-negative` |
| `off` | hollow ring | `--mc-neutral` |
| `busy` | half-filled circle | `--mc-accent` |
The pairing is a contract: `color` recolors a state but never reshapes it, and a theme must never let two states share a
silhouette.
## Sizing [#sizing]
**inline in a sentence**
```tsx
// em-sized, it sits on the text midline
The API is operational.
```
**custom vocabulary**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
An unknown status key renders the `off` ring and logs a dev warning, so a typo never renders as a plausible-looking
wrong state.
## Four homes [#four-homes]
**In a sentence**
```tsx
Checkout is{" "}
{" "}
operational; Billing is{" "}
{" "}
degraded.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
2 / 5
fully healthy
```
**In a tab header**
```tsx
Staging
```
## Accessibility [#accessibility]
The accessible name is the state's label, **"Status: ok."**, composed with your `title` to read "API. Status: ok.". The
interactive entry announces state changes through a polite live region and stays quiet on mount. `pulse` is a CSS halo
that disappears entirely under `prefers-reduced-motion`.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `status` (required) | `string` | Built-in ok \| warn \| error \| off \| busy, or a key of states. |
| `pulse` | `boolean` | Live-now halo (reduced-motion-gated). |
| `states` | `Record` | Extend the vocabulary; the shape+color pairing is preserved. |
| `color` | `string` | Recolors the active state; never reshapes it. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# StreakSpark (/docs/charts/streak-spark)
StreakSpark collapses a sequence of pass/fail outcomes into **runs**, each run a bar whose width is its length on one
shared scale. Streak runs sit low and translucent, breaks sit thin and saturated, and the **current** run is the accent
bar at the right. The record streak carries a small triangle tick, so "are we near our best" reads without counting.
Height and opacity encode run type, never magnitude, which leaves width as the only channel answering how long a run is.
```tsx
`"
>
```
## Install [#install]
```tsx
import { StreakSpark } from "@microcharts/react/streak-spark";
// 1 = passing build, 0 = failing build
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { StreakSpark } from "@microcharts/react/streak-spark/interactive";
```
## When to use it [#when-to-use-it]
Use it for pass/fail run histories, uptime and incident-free streaks, and the current run against a record. A continuous
magnitude belongs in SparkBar and a single completion ratio in Progress.
## Sizing [#sizing]
**default**
```tsx
// data alone → an intrinsic 96×20 box
```
**fixed size**
```tsx
// width & height are viewBox units — they also set the pixel box
```
**responsive**
```tsx
// omit width/height, let CSS drive the width — the viewBox keeps the ratio
```
## Variants [#variants]
`data` accepts booleans, `0`/`1`, or any numbers (they pass on `> 0`, or on `>= threshold`). A `null` is a gap: it
breaks the run and starts a fresh one. Everything else is a prop.
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
= threshold — e.g. daily uptime %, target 99.5
`"
>
```
```tsx
1)}
format={{ useGrouping: true }}
locale="de-DE"
/>`"
>
```
With a `locale`, the count label and the accessible summary follow that locale's own grouping: a 1,204-build streak
reads **"1.204"** in German. The outcome words come from `strings`, so the whole summary localizes together.
## Edge cases [#edge-cases]
```tsx
`">
```
A lone outcome is one current run of length 1, and it is its own record: the summary reads **"Current run 1 passing,
unbroken."**
```tsx
`">
```
When nothing passes there is no completed streak: the strip is one saturated break run, no triangle tick, and the
summary reads **"Current run 5 failing; no completed streak."**
## Four homes [#four-homes]
**In a sentence**
```tsx
Deploy streak{" "}
{" "}
— 7 greens in a row, last fail 3 days ago.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
7
consecutive passes
```
**In a tab header**
```tsx
prod
```
## Accessibility [#accessibility]
The accessible name states the current run, the record, and how often the streak broke: **"Current run 3 passing; record
9; broke 2 times."** The interactive entry roves runs with ← →, announcing each run's length, outcome, and whether it is
the record (**"Run 3 of 5: 4 passing."**). Direction is carried by height, opacity, and color together, never color
alone.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `(boolean \| number \| null)[]` | Outcomes; null is a gap that breaks the run. Numbers pass on > 0. |
| `positive` | `"up" \| "down"` | Which outcome is the streak: pass (up) or fail (down). |
| `threshold` | `number` | With numeric data, values ≥ threshold pass. |
| `label` | `"current" \| "both" \| "none"` | Count labels: the current run, the record too, or neither. |
| `title` | `string` | Accessible name; joins the auto summary. |
| `summary` | `string \| false` | Override or disable the auto summary. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# TallyMarks (/docs/charts/tally-marks)
The chart below prints 23 as four clusters of five plus three loose strokes, the way a person counts. The count reads
back exactly: there is no scale to calibrate and no color to decode. Past `total` the marks stop growing and a `+N`
numeral carries the rest, so a cell never blows out its width. Marks are never resized to fit; width grows with the
count until the cap, then the numeral takes over.
```tsx
```
## Install [#install]
```tsx
import { TallyMarks } from "@microcharts/react/tally-marks";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { TallyMarks } from "@microcharts/react/tally-marks/interactive";
```
## When to use it [#when-to-use-it]
Use it for a small running count in a sentence or cell, a live event or score counter, or an editorial hand-tallied
context. Large magnitudes belong in MiniBar, trends over time in Sparkline, and proportions in Progress.
## Sizing [#sizing]
**hand-drawn pen for an editorial count**
```tsx
```
**cap the width — the numeral tells the truth past total**
```tsx
```
## Variants [#variants]
The ruled pen is the default, because evenly ruled strokes count back fastest in a product table. The `drawn` pen adds a
seeded, deterministic jitter for an editorial voice: the same count always renders identically, on the server and after
hydration, and the jitter only changes how each stroke is drawn, never the count.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
A negative value clamps to 0 and a non-physical value saturates at the 200-mark ceiling. Past `total`, the default
`overflow="numeral"` appends a `+N` so the true total survives even when the marks can't; `overflow="clamp"` suppresses
the numeral for dense columns, and the accessible name still carries the exact count.
## Four homes [#four-homes]
**In a sentence**
```tsx
Petition signatures{" "}
{" "}
— 23 collected, 27 to goal.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
23
of 50 goal
```
**In a tab header**
```tsx
D1
```
## Accessibility [#accessibility]
The accessible name is the exact count, **"17 counted."**, and stays the true value even when the marks overflow to a
`+N` numeral or clamp. The interactive entry announces the new total through a polite live region on change and draws
newly added marks in with a brief, reduced-motion-gated sweep. A count has no sub-parts, so focus reads the summary and
there is no cursor to move.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | The count. Floored; negatives clamp to 0. |
| `total` | `number` | Marks drawn before overflow (default 25). |
| `overflow` | `"numeral" \| "clamp"` | numeral appends +N; clamp stops drawing. The summary always keeps the true count. |
| `pen` | `"ruled" \| "drawn"` | Hand-drawn jitter for editorial contexts. |
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](/docs/quickstart#the-shared-grammar).
# TapeGauge (/docs/charts/tape-gauge)
Airspeed is 142 and climbing, and the reading you need is the current one. TapeGauge parks the value at a fixed center
pointer and scrolls the scale past it, so the current reading is always in the same place. It is the aviation
primary-flight-display tape at word size, and it suits any single live reading: airspeed, throughput, temperature, queue
depth. Semantic zones mark the safe, caution, and danger bands. A stack of chevrons encodes the rate of change on a
channel separate from the level, so a fast climb and a high value never read as the same thing. The visible span stays
fixed while the value updates, so a jump renders as motion of the scale rather than a silent rescale.
```tsx
```
## Install [#install]
```tsx
import { TapeGauge } from "@microcharts/react/tape-gauge";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { TapeGauge } from "@microcharts/react/tape-gauge/interactive";
```
## When to use it [#when-to-use-it]
Use it for a live changing reading with a safe/caution band, where you need the value and its trend together. A history
you want to scan belongs in Sparkline, and a single static number in Delta.
## Sizing [#sizing]
**KPI card**
```tsx
```
**horizontal cell**
```tsx
```
## Variants [#variants]
```tsx
// label="none" drops the in-chart readout for an external number
`"
>
```
```tsx
`"
>
```
`format` also takes `Intl.NumberFormatOptions`. With a `locale`, both the in-gauge readout and the accessible summary
follow that locale's own grouping ("14.200" in German, not "14,200").
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
A non-finite `value` renders an empty gauge: no pointer, no readout. `zones` are optional, and without them the gauge
still tracks level and rate.
## Four homes [#four-homes]
**In a sentence**
```tsx
Airspeed reading{" "}
{" "}
— 142 knots, rising into caution band.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
142
knots ↑
```
**In a tab header**
```tsx
Cruise
```
## Accessibility [#accessibility]
The accessible name states the level, the trend word, and the containing zone: **"Now 142, rising; in the 130–150
zone."** The interactive entry re-announces the reading through a polite live region, throttled so a rapidly changing
value never floods a screen reader, and focus reads the full summary on demand.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | The current level; parked at the pointer. |
| `rate` | `number` | Signed units/tick; drives the chevrons. |
| `zones` | `{ from, to, tone }[]` | Semantic bands on the scale. |
| `span` | `number` | Visible scale extent; fixed while live. |
| `rateTiers` | `[number, number]` | Thresholds for 1 and 2 chevrons (default [span/60, span/15]). |
| `orientation` | `"vertical" \| "horizontal"` | Tape direction (default vertical). |
| `announceEvery` | `number` | (interactive) Minimum ms between live-region announcements as the value streams (default 5000). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Thermometer (/docs/charts/thermometer)
Thermometer fills a ticked tube from the bottom of its range, and the ticks calibrate the read, which is what buys the
precision. The fill always anchors at the bottom of the domain: never re-zeroed, never log. An optional target line
crosses the tube to mark the goal. `domain` defaults to `[0, 100]` because a calibrated instrument needs a stated range,
and auto-fitting would move the scale under the reader. The bulb is instrument chrome and always full; it is not data,
so its area means nothing.
```tsx
```
## Install [#install]
```tsx
import { Thermometer } from "@microcharts/react/thermometer";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Thermometer } from "@microcharts/react/thermometer/interactive";
```
## When to use it [#when-to-use-it]
Use it for a fundraising or goal-progress read, a capacity or utilization gauge in a cell, or any value against a stated
range. Trends belong in Sparkline and proportions of a whole in SegmentedBar; it reads a single value, so many series
need a different chart.
## Sizing [#sizing]
**horizontal, in a table cell**
```tsx
```
**explicit calibration ticks**
```tsx
```
## Variants [#variants]
Vertical with the bulb is the default, since a filling tube reads as progress toward the goal.
`orientation="horizontal"` fits a table cell where a vertical tube cannot, and `bulb={false}` drops the reservoir where
the instrument metaphor is too much for the context.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`"
>
```
A value beyond the domain clamps the fill, and the accessible name still reports the true number, so the reading is
never silently clipped. The accessible summary's value, domain, and target all go through the same locale-aware
formatter, so "7,234" becomes "7.234" in German grouping.
## Four homes [#four-homes]
**In a sentence**
```tsx
Fundraiser progress{" "}
{" "}
— $72K raised, $8K from goal.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
$72K
of $80K goal
```
**In a tab header**
```tsx
Annual
```
## Accessibility [#accessibility]
The accessible name states the value on its scale, **"62 on a 0–100 scale."**, and appends the goal when a target is
set. The interactive entry reveals the exact value on hover or focus, glides the fill to its new level with a
reduced-motion-gated transition, and announces each change through a polite live region.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
Hover or focus also reveals the reading itself in a floating chip, for the sizes and label modes where the mark
does not print it; `readout={false}` drops the chip and keeps everything else.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | The reading. |
| `target` | `number` | A goal tick across the tube. |
| `domain` | `[number, number]` | The calibrated range (default [0, 100]). |
| `ticks` | `number \| number[]` | Tick count or explicit values. |
| `orientation` | `"vertical" \| "horizontal"` | Horizontal fits table cells. |
| `bulb` | `boolean` | Draw the reservoir bulb (default true). |
| `fontSize` | `number` | Type size of the tick and value numerals, in viewBox units (default 8). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# TimeInRange (/docs/charts/time-in-range)
72% in range, 9% below, 19% above. TimeInRange prints the share of a period that stayed inside its corridor and which
side the rest missed on, with the in-range percent leading. Zones sit in a fixed order (below, in, above), so the strip
reads by position first and color second. They are never sorted by magnitude, because which side a miss landed on is
half the reading.
```tsx
```
## Install [#install]
```tsx
import { TimeInRange } from "@microcharts/react/time-in-range";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { TimeInRange } from "@microcharts/react/time-in-range/interactive";
```
## When to use it [#when-to-use-it]
Use it for SLO and uptime corridors, glucose-style time-in-range, and thermal or budget bands. To rank parts use
SegmentedBar; for a single ratio use Progress.
## Sizing [#sizing]
**table cell**
```tsx
```
**clinical column**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
```tsx
`">
```
Severity tiers are drawn in the same hue at greater ink weight, so a severe zone never depends on color alone.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
Percent labels use largest-remainder rounding, so they always sum to 100 and the summary reads the same integers as the
labels.
## Four homes [#four-homes]
**In a sentence**
```tsx
Glucose time-in-range{" "}
{" "}
— 72% in range, 9% below.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
72%
glucose TIR
```
**In a tab header**
```tsx
Patient A
```
## Accessibility [#accessibility]
The accessible name leads with the headline, then the misses: **"72% in range, 7% below, 15% above, 2% severe low, 4%
severe high."** The interactive entry roves the zones, and each announces its share (**"in range: 72%."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `TimeInRangeDatum` | Counts or fractions; normalized to 1. |
| `orientation` | `"horizontal" \| "vertical"` | Vertical suits clinical columns and KPI cards. |
| `label` | `"in" \| "all" \| "none"` | The in-range headline, a full audit, or clean. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# TokenConfidence (/docs/charts/token-confidence)
TokenConfidence underlines each token by how confident the model was. The text itself is the chart, and confidence
resolves to three discrete tiers rather than a continuous ramp. Confident tokens get no mark at all, so reading stays
primary and only the uncertain words draw the eye. The two flagged tiers differ in stroke as well as color: unsure is a
solid underline, guessing a dotted one.
```tsx
`"
>
```
## Install [#install]
```tsx
import { TokenConfidence } from "@microcharts/react/token-confidence";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { TokenConfidence } from "@microcharts/react/token-confidence/interactive";
```
## When to use it [#when-to-use-it]
Use it for LLM answers in chat or transcripts, and for flagging text a reader should review. To audit exact
probabilities use [CalibrationStrip](/docs/charts/calibration-strip); for a single score use Delta.
## Sizing [#sizing]
The text is the chart, so it inherits the surrounding font size — there is no `width`/`height`. Set
`style={{ fontSize }}` or let it flow inline with your prose.
## Variants [#variants]
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
Every token clears the `hi` threshold, so nothing is flagged and the sentence renders as plain, unmarked text. No marks
means no word here needs a second look.
```tsx
`">
```
An empty `data` array renders nothing and reports **"No tokens."** to assistive tech.
## Four homes [#four-homes]
**In a sentence**
```tsx
Model answer confidence{" "}
{" "}
— high on facts, dips on dates.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
98%
```
**In a tab header**
```tsx
facts
```
## Accessibility [#accessibility]
The accessible name is the tier tally: **"4 tokens: 1 confident, 1 unsure, 2 guessing."** The interactive entry gives
each flagged token a roving tab stop; ←/→ move between them (skipping confident tokens), announcing each one's tier and
confidence. Hovering or focusing a flagged token also floats that reading over it as a chip (`guessing 0.22`): the
underline says the token is flagged, the chip gives its tier and number. `readout={false}` drops the chip.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ token, confidence }[]` | Tokens + confidences. |
| `tiers` | `readonly [number, number]` | lo/hi thresholds — the only tuning. |
| `show` | `"flagged" \| "all"` | All also hairlines confident tokens. |
| `legend` | `boolean` | Appends the 1-line inline key. |
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](/docs/quickstart#the-shared-grammar).
# TraceFold (/docs/charts/trace-fold)
A request took 214 ms and you need to know which spans set that number. TraceFold draws one rect per span, placed by its
start time, sized by its duration, and stacked by its nesting depth. Widths sit on one shared linear time scale, never
normalized per row, so a wide span is a slow span. The critical path, the chain of spans that bound the total, is
accented and every other span muted.
```tsx
\`\${Math.round(n)} ms\`}
title="Request trace"
/>`"
>
```
## Install [#install]
```tsx
import { TraceFold } from "@microcharts/react/trace-fold";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { TraceFold } from "@microcharts/react/trace-fold/interactive";
```
## When to use it [#when-to-use-it]
Use it for request traces, flame charts, and p95-exemplar latency breakdowns. For a single duration use Bullet; for a
time series use Sparkline.
## Sizing [#sizing]
**endpoint cell**
```tsx
```
**structure audit**
```tsx
```
## Variants [#variants]
```tsx
\`\${Math.round(n)} ms\`}
/>`"
>
```
`emphasis="none"` drops the accent when the nesting, not the latency, is what you are reading. The critical-path walk
itself is deterministic: ties resolve to the earliest start.
```tsx
`"
>
```
`format` also takes `Intl.NumberFormatOptions` — with a `locale`, the accessible summary's numbers follow that locale's
own grouping and decimal marks ("14.868 ms" in German, not "14,868 ms"). In-rect text stays the plain span label either
way; only the announced numbers are localized.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`"
>
```
A zero-duration span keeps a one-unit floor so it stays visible. That is the only width distortion in the chart.
## Four homes [#four-homes]
**In a sentence**
```tsx
Request trace{" "}
{" "}
— 214 ms total, DB span dominates.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
214ms
total trace
```
**In a tab header**
```tsx
/api
```
## Accessibility [#accessibility]
The accessible name gives the biggest span and its path status: **"4 spans over 214 ms; longest db.query (86 ms) on the
critical path."** The interactive entry roves spans within a depth with ←/→ and between depths with ↑/↓, announcing each
span's duration, share of the total, depth, and whether it is on the critical path.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `Span[]` | Flat span list; parent = index. |
| `emphasis` | `"critical" \| "none"` | Mute non-critical spans, or uniform. |
| `labels` | `boolean` | Width-gated in-rect labels. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# TreeRings (/docs/charts/tree-rings)
TreeRings shows how growth accumulated period over period, the way a tree records it: one ring per period, oldest at the
center, each ring's **thickness** carrying that period's value. The channel is thickness, not area: equal thickness at a
larger radius spans more area (the ring illusion), so read the thicknesses rather than the wedges. The current period is
drawn 1.5× heavier in the accent color, weight and color together. For exact per-period reads use `SparkBar`.
```tsx
```
## Install [#install]
```tsx
import { TreeRings } from "@microcharts/react/tree-rings";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { TreeRings } from "@microcharts/react/tree-rings/interactive";
```
## When to use it [#when-to-use-it]
Use it for account or company age, a cohort-age marker in a table cell, or a per-period growth story in a KPI card.
Avoid it for exact per-period reads (SparkBar), for more than twenty-four periods, and for non-cumulative series.
## Sizing [#sizing]
**filled annuli for print / e-ink**
```tsx
```
**cohort age — total sets the expected lifetime**
```tsx
// this account is part-grown
```
## Variants [#variants]
```tsx
`"
>
```
Stroke rings are the default: at twenty-four pixels, hairlines keep the disc quiet and let the accent ring stay loud.
`rings="fill"` draws filled annuli instead. Pass `total` for the cohort-age story, and the disc fills only Σdata/total
of the radius, so a young account reads as part-grown.
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
TreeRings enforces no minimum visual thickness. A near-zero period looks near-zero, and a zero-value period collapses
its two boundaries onto each other.
## Four homes [#four-homes]
**In a sentence**
```tsx
Account age{" "}
{" "}
— 8 years, last year the thickest ring.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
8 yr
account tenure
```
**In a tab header**
```tsx
Acme
```
## Accessibility [#accessibility]
The accessible name summarizes the growth: **"8 periods; latest 14, biggest 22 in period 5."** The interactive entry
steps the rings from the center out with ←/→ (or hover), announcing each period as "Year 5: 22."
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Per-period growth, oldest first. |
| `highlight` | `"last" \| "none" \| number` | Which period's ring to pick out. |
| `total` | `number` | Expected lifetime Σ — the disc fills only Σdata/total. |
| `rings` | `"stroke" \| "fill"` | Boundary rings (default) or filled annuli. |
| `periodWord` | `string` | Singular period noun for the summary (default "period"). |
| `unit` | `string` | Plural period noun for the summary (default "periods"). |
| `size` | `number` | Rings box edge in viewBox units (default 24). |
| `fontSize` | `number` | Type size of the gutter label, in viewBox units. Defaults from `size`. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# TrendArrow (/docs/charts/trend-arrow)
A latency number moved 8% and the table cell has room for one glyph. TrendArrow encodes the direction in the shape: up,
down, or a flat bar. Color reinforces the shape, so the read survives grayscale, print, and forced-colors. The glyph
never scales with magnitude: an arrow twice as long would claim precision this size can't carry. When the magnitude
matters, reach for [Delta](/docs/charts/delta) instead.
```tsx
`"
>
{"Latency "}
{" improved again this week."}
```
## Install [#install]
```tsx
import { TrendArrow } from "@microcharts/react/trend-arrow";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { TrendArrow } from "@microcharts/react/trend-arrow/interactive";
```
## When to use it [#when-to-use-it]
Use it for table direction columns, dense dashboards, and inline movement cues. For exact magnitudes use Delta; for the
shape of a series use Sparkline.
## Sizing [#sizing]
The glyph is a fixed 16-unit box that scales like an image — size it with CSS. With `showValue` the viewBox widens to
reserve a gutter for the number, so text never paints outside the chart.
**matches the text**
```tsx
// size the glyph in em so it rides the surrounding type
Errors
```
**with the number**
```tsx
// showValue widens the viewBox for the formatted value
```
## Variants [#variants]
Three glyph weights for three densities, a declared noise floor, and the number itself when the glyph alone is too
terse. The default arrow keeps its shaft and head legible at 8 px and in forced-colors, where lighter marks blur.
```tsx
`"
>
```
```tsx
`"
>
```
`flatBand` is where you declare your noise floor, so a ±0.1% wiggle reads as no change instead of a trend.
```tsx
`">
```
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
`format`/`locale` reach `showValue`'s number and the accessible summary's magnitude together — "12,483" becomes "12.483"
in German grouping.
## Four homes [#four-homes]
**In a sentence**
```tsx
P95 latency{" "}
{" "}
is up 9% since the deploy.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
0.6%
of requests
```
**In a tab header**
```tsx
API
```
## Accessibility [#accessibility]
The default accessible name is the direction: **"Up 12%."**, **"No change."** within the flat band, **"No data."** for
non-finite input. `positive="down"` flips only the color, never the glyph or the words. The interactive entry announces
direction changes through a polite live region and pulses the glyph, gated on reduced motion.
This chart is a single unit, so there is nothing to rove between: a click, tap, `Enter` or `Space` selects it
and fires `onSelect`, and no selection stays pinned. That is the scalar half of the shared
[interaction contract](/docs/accessibility#one-interaction-contract).
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `value` (required) | `number` | Signed change; sign → direction, magnitude only via showValue/summary. |
| `flatBand` | `number` | Noise floor: \|value\| ≤ flatBand renders the flat glyph. |
| `glyph` | `"arrow" \| "triangle" \| "chevron"` | Mark weight: default legibility, dense cells, inline text. |
| `showValue` | `boolean` | Append the formatted value in a right gutter. |
| `positive` | `"up" \| "down"` | Which direction is good (colors only, never the glyph). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# VolumeProfile (/docs/charts/volume-profile)
Activity piled up somewhere in the 140s, and a time axis cannot tell you which level. VolumeProfile is a histogram
turned perpendicular to the usual trend axis: the level runs vertically, and bars extend horizontally by the mass of
activity at each level. The modal level, the point of control or POC, is the only accented mark. The value area (the
levels holding most of the activity) is shaded behind it.
```tsx
`"
>
```
## Install [#install]
```tsx
import { VolumeProfile } from "@microcharts/react/volume-profile";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { VolumeProfile } from "@microcharts/react/volume-profile/interactive";
```
## When to use it [#when-to-use-it]
Use it for volume-at-price, level-of-activity distributions, and load by tier. For a time series, or whenever the timing
is what matters, use Sparkline.
## Sizing [#sizing]
**level-activity cell**
```tsx
```
**right side (pair with trend)**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
`valueArea` defaults to `0.7`, and the summary states the fraction it used. It is a stated convention, not an implied
confidence interval.
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
## Four homes [#four-homes]
**In a sentence**
```tsx
Trades at{" "}
{" "}
— POC 142.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
142
```
**In a tab header**
```tsx
AAPL
```
Best at KPI/card scale — profile needs a near-square seat.
## Accessibility [#accessibility]
The accessible name states where activity concentrates: **"Activity concentrates at 142.33 (POC); 70% within
140.33–145.67."** (the right-aligned demo's 5-level profile, default 12 bins). An even distribution reads **"Activity is
evenly spread."** The interactive entry roves the levels with ↑/↓, announcing each level's activity mass alongside its
share and flagging the POC.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ level, weight }[] \| number[]` | Activity mass per level, or raw levels. |
| `valueArea` | `number` | Mass fraction of the shaded value area (0.7). |
| `align` | `"left" \| "right"` | Which way bars grow. |
| `label` | `"poc" \| "none"` | The POC level beside the accent bar. |
| `bins` | `number` | Number of histogram bins (default 12). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Waterfall (/docs/charts/waterfall)
60 opening, five signed steps, 87 at the close. Waterfall is the P\&L bridge in a table cell: one bar per delta, each
starting where the last one left off. Sign is encoded by vertical direction from the running level AND by valence color,
never color alone.
```tsx
```
## Install [#install]
```tsx
import { Waterfall } from "@microcharts/react/waterfall";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Waterfall } from "@microcharts/react/waterfall/interactive";
```
## When to use it [#when-to-use-it]
Use it for P\&L bridges in table cells and net-change decomposition in KPI cards. For unordered category comparison use
MiniBar, and keep any one bridge under about 8 steps.
## Sizing [#sizing]
**no step labels**
```tsx
```
**P&L rows**
```tsx
{quarters.map((q) => (
))}
```
**cost bridge (down is good)**
```tsx
({ label: d.label, value: -d.value }))} positive="down" />
```
## Variants [#variants]
```tsx
`"
>
```
The zero-anchored total bar stays on by default, because floating bars alone can't be checked against a real total.
Hairline connectors carry the running level between steps without adding ink.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
## Four homes [#four-homes]
**In a sentence**
```tsx
Net income bridged from $60k to $87k this quarter{" "}
{" "}
— Product and
Services carried it past Refunds and Opex.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
$87k
from $60k
```
**In a tab header**
```tsx
Revenue
```
## Accessibility [#accessibility]
The accessible name states the endpoints and the split: **"From 60 to 87 over 5 steps: +65 gains, −38 losses."** The
interactive entry steps through the bridge (**"Refunds: −12, running 108."**).
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; value }[]` | Signed deltas in order. |
| `open` | `number` | Opening level (prior-period close). |
| `totalBar` | `boolean` | Zero-anchored closing total bar (default on). |
| `positive` | `"up" \| "down"` | "down" = decreases are good (cost breakdowns). |
| `label` | `"none" \| "delta"` | "delta" (default) prints each step's signed value in a band below the plot; the biggest movers win when labels would collide. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# Waveform (/docs/charts/waveform)
Waveform buckets a high-frequency signal and draws each bucket's maximum, never its mean, so one spike survives the
squeeze down to sparkline width. Averaging would erase the spike someone is looking for. The auto domain is symmetric at
±max|data|, and the peak is disclosed in the accessible summary.
```tsx
(i === 126 ? 0.82 : Math.sin(i / 3) * 0.15 + Math.sin(i / 11) * 0.35) *
(1 - Math.abs(i - 100) / 260),
)}
title="Voice memo"
/>`"
>
```
## Install [#install]
```tsx
import { Waveform } from "@microcharts/react/waveform";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { Waveform } from "@microcharts/react/waveform/interactive";
```
## When to use it [#when-to-use-it]
Use it for voice-memo and audio scrubbers, and for high-frequency log or request volume. For exact values use Sparkline;
for categorical state use Hypnogram.
## Sizing [#sizing]
**table cell**
```tsx
```
**envelope**
```tsx
```
## Variants [#variants]
```tsx
Math.sin(i / 3) * 0.4 * (1 - Math.abs(i - 100) / 220))}
mode="envelope"
/>`"
>
```
```tsx
`">
```
With a `locale`, the announced peak follows that locale's own decimal mark: "0,82" in German, not "0.82".
Comparing loudness across rows needs an explicit shared `domain`. Without one, each chart fits its own max, and quiet
data is rescaled to look loud.
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
A single sample renders as one bucket, top-to-bottom. All-zero data renders every bucket at the shared 0.4-unit "silent"
tick height, and the accessible summary says **"Silent."** rather than reporting a zero peak. A `null` behaves the same
as a true zero for any bucket where it is the only sample: `maxPerBucket` skips non-finite values when picking the
bucket's peak, and a bucket with nothing finite in it falls back to that same silence tick. A genuine gap and real
silence therefore render identically here, unlike Sparkline's line break.
## Four homes [#four-homes]
**In a sentence**
```tsx
Voice memo amplitude{" "}
{" "}
— peak 0.82 at 63%.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
0.82
peak · 63% through
```
**In a tab header**
```tsx
Standup memo
```
## Accessibility [#accessibility]
The accessible name discloses the peak: **"Peak 0.398 at 50% through 200 samples."** Pure silence reads **"Silent."**
rather than blank. The interactive entry roves the buckets, announcing each bucket's position and peak amplitude.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Amplitude samples; negatives allowed. |
| `progress` | `number` | 0–1 played fraction; left buckets tint accent. |
| `mode` | `"bars" \| "envelope"` | Envelope draws the min/max area. |
| `mirror` | `boolean` | Mirror around center; false for magnitude-only. |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# WinProbWorm (/docs/charts/win-prob-worm)
A model has the home side at 98% after three lead changes. WinProbWorm plots that series on a **fixed 0–100 axis that is
never truncated**, so a 2-point edge always looks like a 2-point edge. It splits the worm at the 50% line: while your
side leads the line reads accent, when it trails it reads neutral, and a dot marks each lead change. A win-probability
curve is a modelled read, so the summary always says so, "per the supplied model".
```tsx
```
## Install [#install]
```tsx
import { WinProbWorm } from "@microcharts/react/win-prob-worm";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { WinProbWorm } from "@microcharts/react/win-prob-worm/interactive";
```
## When to use it [#when-to-use-it]
Use it for live win or election probability where the lead flips, for a modelled forecast whose crossings are the point,
and for any 0–100 probability you must not truncate. For a raw score or margin use Sparkline, and for a value that isn't
a bounded 0–100 probability use another chart.
## Sizing [#sizing]
**a wire-to-wire lead never crosses 50**
```tsx
```
**a nailbiter hovers around the line**
```tsx
```
## Variants [#variants]
```tsx
`"
>
```
The endpoint carries the current number. At a taller size the biggest single momentum swing also gets a hair connector
and a signed delta; drop that with `markSwing={false}`, and drop the endpoint probability with `label="none"`.
```tsx
`"
>
```
## Edge cases [#edge-cases]
```tsx
`"
>
```
```tsx
`"
>
```
```tsx
`">
```
With a `format` and `locale`, the endpoint label and the summary format their numbers in that locale's own decimal mark:
`[50, 55.5, 61.2, 58.4, 63.5]` in `de-DE` ends at **"63,5%"**. Values outside 0–100 are clamped to the axis and
dev-warn, since a probability cannot exceed 100. A constant series has no crossing and no swing: an all-50 series reads
"even… throughout", any other constant reads "…holds X% throughout". A `null` breaks the worm into disconnected runs the
way a gap should. A single point has no line to draw, so it renders just the endpoint dot and its label, and `data={[]}`
renders the frame with the "no data" summary.
## Four homes [#four-homes]
**In a sentence**
```tsx
Win probability{" "}
{" "}
— home team leads 62%, swing at Q3.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
98%
win probability
```
**In a tab header**
```tsx
Finals G1
```
## Accessibility [#accessibility]
The accessible name states the current leader, their probability, the number of lead changes, and the biggest momentum
swing: **"Per the supplied model, home leads at 98%; 3 lead changes, biggest swing +17 at point 8."** A constant series
reads "Per the supplied model, even at 50% throughout." The interactive entry roves the points with ←/→ (Home/End jump
to the ends), each announcing the leader and probability at that point, with a live readout chip.
The interactive entry follows the shared [interaction contract](/docs/accessibility#one-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 [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | A single win-probability series, clamped to 0–100. |
| `sides` | `[string, string]` | Names for the two sides — [>50, <50]. Default ["A", "B"]. |
| `label` | `"last" \| "none"` | Print the current leader's probability at the endpoint (default "last"). |
| `markSwing` | `boolean` | Mark the biggest momentum swing (default true; seat-gated). |
| `animate` | `boolean` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
# WindBarb (/docs/charts/wind-barb)
Southwest at 32, in one character. WindBarb points the shaft along the bearing and counts the magnitude in barbs: a half
barb, a full barb, and a pennant are fixed quanta, calibrated to the chart's `step`, so counting them gives the
magnitude without a scale to measure against. The per-barb quantum is stated next to every example below.
```tsx
```
## Install [#install]
```tsx
import { WindBarb } from "@microcharts/react/wind-barb";
```
Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.
## Try it [#try-it]
```tsx
import { WindBarb } from "@microcharts/react/wind-barb";
```
## Reading the barb [#reading-the-barb]
WindBarb has no interactive mode. A single glyph carries the whole reading, so its accessible name is the full sentence.
This gallery is the read-back key: each full barb is one `step`, a half barb is half a `step`, and a pennant is five.
```tsx
`">
```
```tsx
`">
```
## When to use it [#when-to-use-it]
Use it for wind or current direction plus strength, traffic flow, net migration, and request routing. For an exact
magnitude add `label`; for a time series use Sparkline.
## Sizing [#sizing]
**table cell**
```tsx
```
**with label**
```tsx
```
## Variants [#variants]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
## Edge cases [#edge-cases]
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
```tsx
`">
```
`format`/`locale` reach the `label="value"` numeral and the accessible summary's magnitude together: the component
builds one formatter and reuses it for both. A near-zero magnitude renders the conventional open circle for calm rather
than a zero-length shaft, and a negative magnitude flips the bearing 180° with a dev warning.
## Four homes [#four-homes]
**In a sentence**
```tsx
Wind at KSFO{" "}
{" "}
— SW 32 kt, three full barbs.
```
**In a table cell**
```tsx
```
**In a KPI card**
```tsx
225°
· 32 kt
```
**In a tab header**
```tsx
Coastal
```
## Accessibility [#accessibility]
The accessible name is the full reading: **"Southwest (225°), magnitude 32."** Calm renders as **"Calm."** The glyph is
self-describing, so rows of barbs get their interaction from the host table or list rather than from the mark.
## Props [#props]
| Prop | Type | Description |
| --- | --- | --- |
| `direction` (required) | `number` | Degrees; 0 = up/north, clockwise. |
| `magnitude` (required) | `number` | Any unit; quantized into barbs. |
| `step` | `number` | Full-barb quantum (each barb = step). |
| `label` | `"value" \| "none"` | Numeric magnitude beside the glyph. |
| `mode` | `"barb" \| "arrow"` | "arrow" swaps quantized barbs for a plain direction arrow + label. |
| `size` | `number` | Barb box edge in viewBox units (default 32). |
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](/docs/quickstart#the-shared-grammar).