# Sizing & scaling (/docs/sizing)

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.

```tsx
// no width/height → each chart's own default box
<Sparkline data={[3, 5, 4, 8, 6, 9]} />
```

## 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.

```tsx
// width & height are viewBox units — they also set the pixel box
<Sparkline data={[3, 5, 4, 8, 6, 9]} width={200} height={48} />
```

> 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:

```tsx
<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:

```tsx
<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](/docs/formatting#scale-the-domain-decision)).

> `.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:

```tsx
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:

```tsx
<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](/docs/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.
