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.
Every chart is a single <svg> with a viewBox and preserveAspectRatio, so scaling is a solved problem: give it a
size in whatever way suits the layout and the geometry follows. There's no ResizeObserver, no layout effect, and no
client JavaScript involved — a static chart scales purely through SVG and CSS.
The three ways to size a chart
| You want… | Do this |
|---|---|
| A sensible default | Pass nothing — every chart has an intrinsic box. |
| An exact pixel size | Set width / height props (where the chart has them). |
| To fill whatever contains it | Add style={{ width: "100%", height: "auto" }}. |
width/height are the common case, not a universal one. Grid charts derive their box from the cell instead —
ActivityGrid, CalendarStrip, GardenGrid and CohortTriangle take cell and gap, Honeycomb takes cell,
and ConfusionGrid takes size. The box then follows from the number of cells, so you tune the cell, not the frame.
Glyph marks (StatusDot, TrendArrow, MoonPhase, DicePips, and friends) have one fixed intrinsic box and are
sized with CSS. And TokenConfidence isn't an <svg> at all — it's text, so it inherits the font size of whatever
it sits in. Everything below about viewBox scaling applies to the SVG charts.
Default: data alone
data alone always renders something correct — at the chart's own intrinsic size. Each chart type ships a default box
tuned to its shape: a Sparkline defaults to a wide, short 80 × 20, while a RubricStrip has no fixed default height at
all — it grows a row per criterion. Good for dropping a mark into a table cell or a sentence without thinking about
dimensions.
Fixed size: width & height
width and height are viewBox units — and because a static chart renders <svg width={…} height={…}>, they also
set the rendered pixel box. So one pair of numbers controls both the coordinate space the geometry is drawn in and the
size on screen.
Because width/height set the viewBox, they change how much room the geometry has — a taller RubricStrip gives each
criterion a thicker row, a wider Sparkline spreads its points further apart. They are layout dimensions, not a zoom
level.
Fill the container
To make a chart fluid, stop pinning its pixel size and let CSS drive the width instead. The viewBox keeps the aspect
ratio, so height: auto follows the width automatically. This is the one-liner for a KPI card, a responsive dashboard
tile, or a full-width hero:
<Sparkline data={data} style={{ width: "100%", height: "auto" }} />The chart now grows and shrinks with its parent. Constrain it by sizing the container (or add a max-width), not the
chart:
<div style={{ width: "100%", maxWidth: 320 }}>
<Sparkline data={data} style={{ width: "100%", height: "auto" }} />
</div>You can still pass width/height alongside fluid CSS — they become the chart's intrinsic aspect ratio and its size
before CSS overrides the width. If you use a utility framework, the equivalent classes work just as well
(className="w-full h-auto").
To give a whole row of charts one identical box rather than sizing each by hand, put width/height on SparkGroup
instead: it enforces them on every series child that didn't set its own (and shares one scale while it's there — see
Formatting & scale).
.mc-root is display: inline-block by default (so a chart flows next to text). When you make one fill a block-level
container, it behaves as expected — but if you ever see it not stretching, that's the reason: give it a display: block or a flex/grid parent.
Interactive charts size the same way
Everything above applies unchanged to the …/interactive entries. An interactive chart wraps its SVG in a focusable
<span> (it owns the pointer, keyboard, and touch gestures for the whole chart), but that wrapper and the SVG are
always the same box: the wrapper is an inline-block that hugs its child, and the composed SVG inside is pinned to
width: 100%; height: auto. Your style merges over the wrapper's base style and your className composes after its
base class, so the same recipe fills it:
import { Sparkline } from "@microcharts/react/sparkline/interactive";
// identical to the static entry — fills its container
<Sparkline data={data} style={{ width: "100%", height: "auto" }} />;Because the wrapper and the SVG share one box, hit-testing is exact at any size: the crosshair, highlight, and readout land under the cursor, a touch drag scrubs the unit actually beneath the finger, and a pinned selection stays on the right unit when the box reflows. An interactive chart is its static twin plus interaction, never a different size.
Inline in a sentence
A word-sized chart in running prose should scale with the font, not a pixel box. Wrap it in className="mc-inline"
(shipped in styles.css) and size it in em so it rides the text:
<p>
Revenue is up this week{" "}
<span className="mc-inline">
<Sparkline data={[3, 5, 4, 8, 6, 9]} style={{ height: "1em", width: "auto" }} />
</span>{" "}
and holding.
</p>mc-inline handles vertical alignment; the em height ties the mark to the surrounding type size, so it stays
proportional wherever the text lands. Alignment is a baseline seat, not a fixed nudge: the wrapper is an
inline-flex box whose only child is the SVG, so it takes its baseline from the SVG's bottom edge and the mark stands
on the text baseline exactly like a glyph. That's typeface-independent — bars sit where letters sit in any font.
Symmetric glyph marks have no bottom edge to stand on, so mc-inline centers those on the cap band instead:
TrendArrow, StatusDot, MoonPhase, DicePips, the radial dials and the other glyph shapes read like an icon set in
running prose. You don't opt in.
Which seat a mark gets isn't guessed from its class name — each chart reports its own plot box, measured in the same
viewBox units it draws in, and the stylesheet seats the mark from that. So a chart whose padding changes can't drift out
of alignment, and a chart that changes shape with its props carries the right seat for each: SparkBar stands on the
baseline in bar mode and centers in win-loss, because only one of those has a real floor. Two tokens let you nudge the
result if a particular typeface needs it — --mc-inline-nudge for every mark, and --mc-glyph-nudge for centered ones
only.
Leave <Delta> bare — no mc-inline wrapper. It renders as inline text and already owns its own baseline.
Strokes stay crisp at any scale
Scaling an SVG normally thickens or thins its lines. microcharts sets vector-effect: non-scaling-stroke on data marks,
so a line drawn at 200 px and the same line at 800 px keep the same visual stroke weight — the geometry scales, the
ink doesn't. Rectilinear marks also use shape-rendering: crispEdges so bars and grids stay sharp. You get responsive
sizing without the usual blurriness or weight drift.
Stroke weight is a token, not a size artifact — set --mc-stroke-width (see Theming) if you want it
heavier or lighter, independent of how big the chart is.
For a whole dense context — a table packed with sparklines — reach for --mc-density: one scalar that scales stroke
weight, label size, and small-multiple gap together (< 1 tighter, > 1 airier). It tunes the ink and type, never the
box, so width/height still own the geometry.
Formatting & scale
Control how a chart shows numbers and dates, and what value range it's drawn against — one format prop (Intl options or a function) feeds the direct labels, the accessible summary and the interactive readout alike, locale handles separators and language, and domain fixes the scale. Formatting is cached, so it stays cheap across hundreds of charts.
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.