Composition
How a microchart sits inside real UI — on your surface, in a sentence, a table cell, a KPI card, or a tab — and how SparkGroup keeps small multiples honest with one shared scale. Every placement decision the gallery makes, written down so you can reproduce it.
microcharts are built to sit inside an interface, not in a chart pane of their own. This page documents the placement decisions the gallery demos make — the surface they need, the size and props each context wants, and how to compare several at once — so nothing is left implied. If a demo on this site looks right, this is why.
Charts sit on your surface
Every chart is transparent: it never paints its own background. That's deliberate — a chart drops onto whatever
surface it lands on and picks up the ink color from --mc-stroke (which tracks light/dark for you). The one decision
that's yours: give it a container with real contrast.
- 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 — comfortably past the 3:1 bar for non-text, and deliberately quieter than the data.) - Don't wrap it in a low-contrast tint and expect the chart to compensate; it won't, by design.
- Need the ink to differ from the page (charts on a colored panel, frosted glass)? Set
--mc-stroke(and valence tokens if needed) on the container — see Theming. Don't override.mc-rootpaint rules.
The gallery renders each demo inside a panel with a normal card background and a hairline border. Nothing about that panel is part of the library — it's just a surface. Your app's cards, table rows, and headers are surfaces too.
In a sentence
A word-sized chart in running prose scales with the font and aligns to the text baseline; drop its endpoint dots and
it reads as one glyph, not a widget. (There are no axes to drop — no chart in the catalog draws one.) Wrap it in
mc-inline (shipped in styles.css), pass dots="none", and mark it decorative with summary={false} so a screen
reader hears the sentence, not a second description.
p95 latency this week — trending down.
See Sizing for the em-based variant that scales with the surrounding type size.
In a table cell
In a dense table, each row already carries a name and a number — the chart's job is the shape between them. So it goes
small, with dots off, decorative (summary={false}), and the numeric column stays tabular-nums so figures line up.
| checkout-api | 31 ms | |
| auth-api | 16 ms | |
| search-api | 78 ms |
In a KPI card
Here the big number leads and the chart is context beneath it. Give it a little more height, turn fill on so it reads
as a filled area at a glance, and keep it decorative — the figure and label already say what it is.
In a tab or button
A spark beside a label previews what's behind the tab. It goes tiny — the label is the primary read, the chart a hint — so drop to a small box with no dots.
Small multiples: one shared scale
The most important composition decision, and the easiest to get silently wrong: when you place several sparklines in a row to compare them, they must share one vertical scale. Auto-scaling each to its own range makes a flat line and a spiking one look identical — a real correctness bug.
SparkGroup fixes it structurally. It reads every child's data, computes one domain across all of them, and
enforces one physical size — so a row of charts is honestly comparable. It's hook-free and RSC-safe, and any child's
own explicit domain/width still wins.
Pass zero when the group is bars or areas (so the shared domain anchors at zero), or an explicit domain={[min, max]}
when you want a fixed frame instead of the data's own range (domain defaults to "shared").
Two details worth knowing. Only children that actually carry a data prop get the scale and size injected — plain
wrappers, labels, and captions pass through untouched, so you can interleave them without them collecting stray DOM
attributes. And the group renders one <div className="mc-group">, an inline-flex row that wraps: its gap is
--mc-gap scaled by --mc-density, and className/style on SparkGroup land on that div if you want a different
layout.
Decorative vs. summarized — the summary decision
You'll notice summary={false} on nearly every demo above. That's a deliberate call, not a habit:
- Standalone chart (the one thing in its region): keep the auto-summary on. It's the accessible name a screen reader announces — real, generated from the data.
- Repeated or captioned chart (table rows, tabs, a chart next to a number that already states it): pass
summary={false}. The surrounding text is the accessible name; a per-chart summary would just make a screen reader read the same thing many times.
This is the only accessibility lever you routinely touch. Everything else — the role="img", the <title>, and the
whole interaction contract on interactive entries (roving arrow keys, select-to-pin with Enter or Space, touch
scrub, live announcements) — is automatic. See Accessibility.
Sizing & scaling
How microcharts sizes itself — the intrinsic default box, width/height as viewBox units, filling a container fluidly with one line of CSS, and sitting inline in a sentence. Strokes stay crisp at any scale.
Annotations
Declarative reference context for React microcharts — thresholds, target zones, moment markers, and callouts, written as children and rendered identically inside every host.