Skip to content
microcharts

Design notes

Why microcharts defaults the way it does — no axes or legends, static-first entries, one shared grammar, generated summaries, size budgets as CI gates, and the bar a new chart type has to clear.

These are the decisions baked into the defaults, so you rarely have to make them yourself. Each one below explains what the library does and why it does it that way.

Direct labels beat chrome

At word size, an axis, a legend, and a gridline each cost more ink than they return, so there are none. A value sits at the end of the line. A min and a max mark themselves. What's left is almost all data.

Areas anchor at zero

A filled Sparkline always measures from zero. An area floating on an arbitrary baseline exaggerates change: the fill grows faster than the numbers do. Anchoring at zero keeps length proportional to magnitude.

Color encodes, never decorates

There's one accent per context, and a color change always means a change in meaning. Positive and negative come from a color-blind-safe pair, doubled by a glyph or a position, so a chart still reads with no color at all. A preset restyles a chart without changing what it says.

Static first, interactivity opt-in

Nearly every chart ships as two entries (WindBarb is static-only). The default is pure SVG: hook-free, listener-free, safe inside a React Server Component, and it renders to HTML with zero client JavaScript. Hover, roving keyboard focus, touch scrub, select-to-pin, and live announcements live in a separate /interactive subpath you import only where you need them. The two entries never mix, so you pay for interactivity only where you ask for it, and a static chart can't drag a listener into your server bundle.

The interactive entry composes the static one rather than re-implementing it, so the two renderings can't drift. Every multi-unit chart resolves a gesture to a navigable unit through the same kernel, so interaction is one thing to learn across the catalog. Single-unit charts have nothing to rove between and take the hover/select half of that contract without the index half.

One grammar, not an options bag

data alone always renders something correct. A prop name means the same thing on every chart — title, color, domain, format, summary — so what you learn on one chart transfers to all of them. Variants are props; a genuinely different data shape is a different component rather than a deeper options object. Annotations are children you nest: a threshold, a target zone, a marker. The result is an API you can mostly guess.

Generated summaries

Each chart generates a natural-language sentence from its data, and that one deterministic sentence does three jobs: it is the accessible name a screen reader speaks, the text a crawler indexes, and the string a model can quote back. It degrades on empty, single, and flat data instead of describing a chart that isn't there. See Accessibility for the details.

Small is a design material

Composition, contrast, and the auto-summary all have to survive the smallest supported size. At that size there's no room for a mark that isn't carrying information, so decoration never gets in.

Motion lives in the interactive build

Static charts have no animate prop and run no animation JavaScript. Two things move inside a static host, both pure CSS and both stilled by reduced motion: <Marker celebrate>, a one-shot burst on a milestone crossing, and StatusDot pulse, the catalog's one looping animation and its one documented exemption from the rule below. A monitoring dot that pulses reads as a live feed, and the same dot holding still reads as a stopped one, so the loop carries the reading. It's opt-in per instance and off by default.

Only the /interactive entries play an entrance, and only when you opt in with animate (off by default). It runs through the Web Animations API, gated on prefers-reduced-motion. Every entrance follows the logic of the data it draws: a line draws left-to-right along its time axis, bars grow from the baseline, a donut fills clockwise from twelve o'clock, and labels arrive last, because they annotate marks that already exist. Marks decelerate into place. They never bounce, loop, or pull the eye away from the data.

Budgets are build gates

Size is a CI gate. A chart that grows past its gzip budget fails the build, the same way a failing test does, and the SSR benchmark exits non-zero when a chart drops below its render floor. The numbers on the Performance page are never hand-typed: the sizes are re-measured from the built dist/ on every CI run, and the throughput figures are derived from the checked-in benchmark result, so a claim can't drift from what was measured.

One package

All the chart types ship from a single package, @microcharts/react, each behind its own subpath so you only bundle what you import. The catalog's collections — core, decision, expressive, frontier — are metadata for browsing, never package or import boundaries. There's no plugin to install and no engine to configure.

The root barrel (@microcharts/react, no subpath) carries only the shared pieces, so importing it pulls in no chart code: the Chart root wrapper every chart composes — a role="img" <svg> with the viewBox, title, and generated summary already wired, there if you ever need to build a custom mark — plus SparkGroup for shared-scale small multiples, MicroProvider for theming, and the describeSeries / makeFormatter / EN helpers documented under Formatting and Accessibility.

The bar for a new chart type

A new chart earns its place only if it clears a real bar: it reads at word size (roughly 200×60 px), it tells a data story the catalog can't already tell well, it has one honest primary encoding channel, and a first-time reader can read it back without training. This is why the catalog prefers uncommon questions over uncommon shapes.

What we don't ship

No pie, no needle gauge, no battery, no waffle, no violin, no 3-D, no gradients-as-decoration, no drop shadows on data, no looping animation (with the one exemption noted above, where the loop carries the reading). These aren't theme options you can switch on. They're designed out, because at word size they mislead more than they inform. Where a rejected chart answered a real question, the catalog ships a replacement: Bullet or TapeGauge instead of a speedometer, SegmentedBar for comparative part-to-whole, MicroDonut as a capped icon-size mix when a glyph is specifically wanted, and MicroBox instead of a violin.