Skip to content
microcharts

Sizing & scaling

How to size a chart — the intrinsic default box, width and height as viewBox units, filling a container with one line of CSS, and sitting inline on a line of text.

Every chart is a single <svg> with a viewBox and preserveAspectRatio. 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: a static chart scales through SVG and CSS alone.

The three ways to size a chart

You want…Do this
A sensible defaultPass nothing — every chart has an intrinsic box.
An exact pixel sizeSet width / height props (where the chart has them).
To fill whatever contains itAdd style={{ width: "100%", height: "auto" }}.

Not every chart has width/height. Grid charts derive their box from the cell: 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 rather than the frame. Glyph marks (StatusDot, TrendArrow, MoonPhase, DicePips, and the rest) have one fixed intrinsic box and are sized with CSS. And TokenConfidence isn't an <svg> at all: it renders 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 renders 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 and grows a row per criterion. Use it to drop a mark into a table cell or a sentence without picking dimensions.

Intrinsic size
interactive · 6.94 kB · static · 4.25 kB

Fixed size: width & height

width and height are viewBox units. A static chart renders <svg width={…} height={…}>, so they also set the rendered pixel box: one pair of numbers controls both the coordinate space the geometry is drawn in and the size on screen.

Fixed size
Weekly revenue

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, drop its pixel size and let CSS drive the width instead. The viewBox keeps the aspect ratio, so height: auto follows the width. 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. To constrain it, size the container or give it a max-width:

<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. Utility classes work the same way (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: it enforces them on every series child that didn't set its own, and shares one scale at the same time. See Formatting & scale.

.mc-root is display: inline-block by default, so a chart flows next to text. Filling a block-level container works anyway; if you ever see one refuse to stretch, that's the reason. Give it 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> that owns the pointer, keyboard, and touch gestures for the whole chart, and 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 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, at the same size.

Inline in a sentence

A chart in running prose should scale with the font rather than 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. The alignment is a baseline seat rather than 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 like a glyph. That holds in any typeface.

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 is never 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 bar mode has a floor. Two tokens nudge the result if a particular typeface needs it: --mc-inline-nudge for every mark, --mc-glyph-nudge for centered ones only.

Leave <Delta> bare, with 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 at any size.

Stroke weight is a token: set --mc-stroke-width (see Theming) to make it heavier or lighter, independent of how big the chart is.

For a whole dense context, a table packed with sparklines, use --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.