Skip to content
microcharts

Quickstart

Install @microcharts/react — the React sparkline and microchart library. Import styles once, render an accessible SVG chart in a Server Component or add /interactive for hover and keyboard.

Set up

Prefer an agent — paste the prompt once. It installs the package, imports the stylesheet at your app root, records the conventions in your agent-rules file, and verifies with a first chart. Or do the two steps yourself under Set up manually.

Set up with an AI agent

Hand the prompt below to your coding agent — Cursor, Claude Code, GitHub Copilot, Codex, or any model, large or small. Works on a fresh repo or an existing one. It's also served verbatim at /agent-setup.md.

Paste into your agent
Set up **@microcharts/react** in this repo — word-sized, zero-dependency React charts (React 18 or 19 peer, ESM-only).
Complete every step in order; don't stop to ask.

## 1 · Install

- If this repo is not already a React 18/19 app, scaffold one first (Vite `react-ts` or Next.js), then continue.
- Install `@microcharts/react` with this repo's package manager.
- Import the stylesheet once at the app root (both package + stylesheet are required):
  - Next.js App Router: `app/layout.tsx`
  - Next.js Pages Router: `pages/_app.tsx`
  - Vite / other React apps: the entry file (e.g. `src/main.tsx`) `import "@microcharts/react/styles.css"`
- Charts inherit the page font. If labels look like a serif fallback, set `:root { --mc-font: system-ui, sans-serif; }`.

## 2 · Docs — fetch small, on demand

- https://microcharts.dev/llms.txt — index: every chart + guide links. Read this now.
- https://microcharts.dev/catalog.json — full API. Do **not** read the whole file. Find the chart by `slug`, then use
  `sharedProps` + that chart's `props`. If the chart has `sharedInteractive` / the document has `howToRead`, follow
  those; otherwise use the chart's `.md` for interactive callbacks. Never invent props.
- https://microcharts.dev/docs/charts/<slug>.md — one chart's page (import path, edge cases). Fetch only for charts you
  use — pick the slug from the index.
- https://microcharts.dev/docs/quickstart.md — grammar + interactive contract, if you need more than the rules below.

If you can't fetch URLs, skip this step — the rules below are enough to render a first chart correctly.

## 3 · Record the conventions

Append the rules below as a `## microcharts` section in `AGENTS.md` (create the file if missing — Cursor, Copilot, and
Codex read it). If this repo uses Claude Code, mirror it: `ln -s AGENTS.md CLAUDE.md` on macOS/Linux, or copy the
section into `CLAUDE.md` on Windows.

- One chart = one subpath import, e.g. `import { Sparkline } from "@microcharts/react/sparkline"`. The default export is
  static — RSC-safe, zero client JS. Add `/interactive` only when hover, keyboard, touch, or selection is needed. That
  entry is a client component: in Next.js App Router, import it from a `"use client"` file. WindBarb is static-only.
- `data` alone always renders; single-value charts take `value` instead. Shared prop names mean the same thing
  everywhere — see `sharedProps` in `/catalog.json`. Chart-specific props and callbacks live on the chart's catalog
  entry and its `.md` page; don't memorize them.
- Interactive entries share one contract: hover/arrows activate; click/tap/Enter/Space select; Escape clears. Prefer
  each chart's `sharedInteractive` list when present (lean scalars may take only `onSelect`; MinimapStrip uses
  `onWindowChange` in chart `props`). For entrance motion: pass `animate` and import `@microcharts/react/motion` once on
  the client.
- `null`, `NaN`, or non-finite values in `data` are gaps, never zeros.
- Accessibility is automatic (`role="img"` + a generated summary). Inline in a sentence: wrap in
  `<span className="mc-inline">` and pass `summary={false}`.
- Theme with `--mc-*` or `data-mc-theme` (omit for default), or `defineTheme` from `@microcharts/react/theme`. Charts
  paint no background. Prefer tokens; `color` / categorical `colors[]` are valid when the chart's docs say so.
- Annotations are children: `Threshold`, `TargetZone`, `Marker`, `Callout` from `@microcharts/react/annotations`.
- Never add another charting dependency. No pie or gauge — use `Bullet`, `SegmentedBar`, or `MicroDonut`.

## 4 · Verify

Render `<Sparkline data={[3, 5, 4, 8, 6, 9]} title="Weekly revenue" />` in a real page, then run the dev server or
build. Self-check:

- Chart missing or unstyled → the styles.css import isn't at the root (step 1).
- Labels render in a serif font → set `--mc-font` (step 1).

Finish with a short report: the installed version, the file importing styles.css, the file holding the conventions, and
the page rendering the test chart.

The prompt fetches small, targeted files — the /llms.txt index first, then one chart's .md page at a time — so it stays inside the context window of smaller models and works from today's real API instead of a memorized guess. The rules are inlined too, so it still works when the agent can't reach the network.

Set up manually

Two steps, once, at your app root — package plus stylesheet. Both are required, and both fit in the next minute.

1 · Install. React 18 or 19 is a peer dependency; there are no other runtime dependencies.

pnpm add @microcharts/react

2 · Import the stylesheet once at the root of your app — it carries every theming token and chart style in a low-specificity cascade layer, so your own styles always win. Where "root" means depends on your framework — anywhere that runs once, before any chart renders:

app/layout.tsx
import "@microcharts/react/styles.css";
src/main.tsx
import "@microcharts/react/styles.css";

Charts inherit your page's font. If the surrounding page never sets a font-family (some minimal starters and sandboxes don't), SVG text falls back to a serif and labels look wrong — give your page a font, or set one just for charts:

:root {
  --mc-font: system-ui, sans-serif;
}

Full detail — why it inherits, and per-scope fonting — in Theming.

Your first chart

Every series chart renders from data alone — single-value charts take value. Either way it works in a React Server Component with zero client JavaScript — it's pure SVG, and its accessible name is generated from the data.

Static · RSC-safe
interactive · 6.64 kB · static · 3.99 kB

Try it live

No install needed to feel it out. Open a full Vite + React app with the package installed in StackBlitz — static and interactive charts, inline and block, four themes, running in your browser.

Live · full app, in the browser

A full Vite + React app with @microcharts/react installed — static and interactive charts, inline and block, four themes. No local setup.

The shared grammar

One idea runs through the whole catalog: the same prop name means the same thing on every chart. Learn these once and they transfer everywhere — a new chart type is a new data shape, never a new vocabulary. The data prop alone always renders something correct; everything else is opt-in.

PropTypeWhat it does
data(number | null)[] | per-chart shapeThe series. null / NaN are gaps, never zeros. The one required prop; each chart documents its own shape when it isn't a plain number array.
domain[min, max]Fix the value range instead of auto-fitting — see Formatting & scale.
colorstringMark color: any CSS color or a token ("var(--mc-accent)"). Semantic tokens keep their meaning — see Theming.
titlestringNames the chart — an SVG <title>, so it becomes the accessible name and the hover tooltip, not visible text.
summarystring | falseOverride the auto-generated accessible sentence, or pass false to mark the chart decorative — see Accessibility.
formatIntl.NumberFormatOptions | (n) => stringHow numbers render in labels and summaries — see Formatting & scale.
positive"up" | "down"Which direction is good, so color and arrows point the honest way (lower latency is "down").
labelper chartA short direct label next to the mark, in a reserved gutter — never an axis or legend.
dotsper chartWhich individual points to emphasize (e.g. "minmax" on a line).

Not every prop applies to every chart — a bar chart has no dots, and the single-value charts take value rather than data — but wherever a concept exists, its prop name and meaning are always these. Each one has a deeper home: Formatting & scale owns format and domain, Theming owns color, and Accessibility owns summary.

Alongside the grammar sit the layout props — className and style on every chart, plus width and height on the charts whose geometry is sized that way (the fixed-glyph ones size from style or their own size / cell knob) and id, which opts into <title>/<desc> + aria-labelledby naming; see Sizing. The i18n props are locale (BCP-47, for number and date formatting) and strings (the summary/label templates), plus seriesStrings on the multi-series charts — see Accessibility. Everything else is specific to a chart's own shape and listed in the prop table on its page. To place charts in real UI — or render several on one shared scale with SparkGroup — see Composition. Annotations are children: Threshold, TargetZone, Marker, Callout.

Add interactivity

Need hover, keyboard navigation, touch, or live announcements? Import the same chart from its /interactive entry. The rendered output and the accessible name are identical — the interactive entry composes its static twin rather than re-implementing it — and it adds props rather than changing any: the interaction callbacks below, plus animate, strings on the charts whose static entry doesn't take it, live on the single-value charts, and onWindowChange on MinimapStrip. The interactive entry is a client component: in a Next.js App Router page, render it from a file with "use client" at the top (or anywhere inside one).

"use client";

import { Sparkline } from "@microcharts/react/sparkline/interactive";

<Sparkline data={[3, 5, 4, 8, 6, 9]} title="Weekly revenue" />;

One interaction contract

Every interactive chart behaves the same way, so you learn it once. Hover or arrow keys make a unit active; a click, tap, Enter, or Space selects it and pins the readout so it survives blur. Escape clears, Home/End jump to the ends, and arrows rove in both axes (←/↑ previous, →/↓ next). On touch, a tap pins and a drag scrubs. What a "unit" is depends on the chart — a point, a bar, a day cell, a segment — and each chart's page names its own.

PropTypeWhat it does
onActive(datum: MicroDatum | null) => voidThe active unit changed (pointer or keyboard). null when the chart clears.
onSelect(datum: MicroDatum | null) => voidA unit was selected or unselected — click, tap, Enter, Space, Escape.
selectedIndexnumber | nullControl the selection yourself.
defaultSelectedIndexnumber | nullSeed the selection once, then let the chart own it.
readoutbooleanShow the floating value chip on hover (default true). false hides only the chip.

MicroDatum is { index, value, label?, formatted? }. Two of the fields carry the value in different forms, so you pick the one that fits: value is the raw primary number — compute with it (null when the unit encodes nothing); formatted is the ready-to-display string the chart's own readout would show, built with your format/locale — render it. For a plain chart that's just the value ("$0.53"); for a two-part unit it's the whole readout ("48 → 68 (up 42%)"). index identifies the unit (point, bar, cell, segment…), and label is its human name where one exists ("Berlin", "Checkout"). Same shape on every chart and both callbacks.

Pair the two to lift the value out of the chart. Set readout={false} to keep the hover crosshair but suppress the in-chart chip, and read datum.formatted in onActive to render that value wherever you like — a KPI number, a sentence, a table cell — without re-deriving format/locale yourself:

const [spend, setSpend] = useState("$0.53");
<Sparkline data={cost} format="$,.2f" readout={false} onActive={(d) => d && setSpend(d.formatted!)} />;

Scalar charts with a single unit — Delta, Progress, StatusDot, Bullet and the rest — take onSelect only; there's nothing to rove between. Fifteen of them also take live (default true): as the value updates, the new reading is announced through a polite live region, throttled so a streaming number never spams a screen reader. Not all of them do — Bullet's only interaction prop is onSelect — so check the chart's own page. Two charts opt out of the picker on purpose: MinimapStrip is a range slider with its own onWindowChange([lo, hi]), and TokenConfidence moves real focus through inline text spans. Full detail, including the live-region behavior, is on Accessibility.

Animate on mount (optional)

Interactive entries also take an animate prop — the chart draws on instead of appearing instantly. It's off by default, and inert on the server and on hydrated server HTML, so it never fights SSR or flashes over painted content; a fresh client-side mount is what plays it. Wire the engine once, client-side, then opt in per chart:

"use client";

import "@microcharts/react/motion"; // once, client-side
import { Sparkline } from "@microcharts/react/sparkline/interactive";

<Sparkline data={[3, 5, 4, 8, 6, 9]} title="Weekly revenue" animate />;

prefers-reduced-motion wins unconditionally — a reader who asked for less motion gets the finished chart, never the entrance.

Import paths

Import each chart from its own subpath so you only ship what you use. Nearly every chart follows the same two-entry pattern — a static default and an /interactive twin (WindBarb is the lone static-only exception):

ChartStaticInteractive
Sparkline@microcharts/react/sparkline@microcharts/react/sparkline/interactive
SparkBar@microcharts/react/sparkbar@microcharts/react/sparkbar/interactive
Delta@microcharts/react/delta@microcharts/react/delta/interactive
Bullet@microcharts/react/bullet@microcharts/react/bullet/interactive
ActivityGrid@microcharts/react/activity-grid@microcharts/react/activity-grid/interactive

Every other chart uses the same shape (WindBarb ships static-only, with no /interactive twin) — see All charts for the full catalog, each page listing its own import path and props.

Compatibility

  • React 18 or 19 as a peer dependency — no other runtime dependencies, ever (dependencies: {}, CI-enforced).
  • ESM-only. The package ships "type": "module"; there's no CommonJS build.
  • Server-component and SSR-safe. Static entries are hook-free and listener-free, so they render to plain SVG on the server with zero client JavaScript — no "use client" needed unless you import from /interactive.
  • Browser floor: evergreen, ES2022. That includes Safari from version 16.4 — a couple of ES2022 array methods used internally crash on older Safari, so that's the practical minimum, not just a tsconfig setting.