# StackedArea (/docs/charts/stacked-area)

Traffic splits between mobile, web, and API, and the question is whether that mix is moving. StackedArea takes up to
three labeled series over the same ordered points and normalizes each to a share before stacking it, so layer thickness
is share and the stack always sums to 100%. That keeps the read on composition rather than on magnitude and composition
together, and the shift over time is what the shape shows. Three series is the hard cap: beyond that, thickness at 16 px
stops being readable.

```tsx
<StackedArea
  data={[
    { label: "Mobile", values: [30, 34, 36, 40, 44, 47, 52, 56, 58, 60, 63, 66] },
    { label: "Web", values: [50, 48, 47, 45, 42, 41, 38, 36, 35, 33, 32, 30] },
    { label: "API", values: [20, 18, 17, 15, 14, 12, 10, 8, 7, 7, 5, 4] },
  ]}
  title="Traffic mix"
  width={220}
  height={30}
/>
```

## Install

```tsx
import { StackedArea } from "@microcharts/react/stacked-area";

<StackedArea data={mix} title="Traffic mix" />
```

Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.

## Try it

```tsx
import { StackedArea } from "@microcharts/react/stacked-area/interactive";

<StackedArea
  data={mix}
/>
```

## When to use it

Use it for traffic or revenue mix in KPI cards and share-shift reads in a sentence. Four or more series need a different
chart; for exact values over time use a SparkGroup of Sparklines.

## Sizing

**mix rows**

```tsx
{regions.map((r) => (
  <StackedArea key={r.id} data={r.mix} title={r.name} />
))}
```

**ridge skin**

```tsx
<StackedArea data={mix} mode="ridge" />
```

## Variants

`order="asc"` puts the smallest series on top, where curvature distorts thickness the least.

```tsx
// same stack throughout — a ridge skin, smallest-on-top order, and a localized read
const mix = [
  { label: "Mobile", values: [30, 34, 36, 40, 44, 47, 52, 56, 58, 60, 63, 66] },
  { label: "Web", values: [50, 48, 47, 45, 42, 41, 38, 36, 35, 33, 32, 30] },
  { label: "API", values: [20, 18, 17, 15, 14, 12, 10, 8, 7, 7, 5, 4] },
];

<StackedArea data={mix} mode="ridge" />
<StackedArea data={mix} order="asc" />
<StackedArea data={mix} locale="de-DE" label="last" />
```

`format`/`locale` control the share numbers: the accessible summary and endpoint labels follow the locale's own percent
formatting ("66 %" in German, with a space before the sign, not "66%"). The stack itself never changes shape; only the
announced and printed numbers localize.

## Edge cases

```tsx
<StackedArea data={[{ label: "A", values: [10] }, { label: "B", values: [5] }]} title="Single column" />
```

```tsx
<StackedArea
  data={[
    { label: "A", values: [0, 0, 0, 0] },
    { label: "B", values: [5, 5, 5, 5] },
  ]}
/>
```

A single column still stacks normally: share is well-defined from one point, so it renders as a vertical slice with no
width to run across. A series pinned at zero collapses to a zero-height layer instead of distorting the others, and the
remaining series still sum to 100% of what's left.

## Four homes

**In a sentence**

```tsx
<p>
  Traffic mix this year{" "}
  <span className="mc-inline">
    <StackedArea data={mix} width={80} height={16} summary={false} />
  </span>{" "}
  — mobile overtook web, now 66% of sessions.
</p>
```

**In a table cell**

```tsx
<td>
  <StackedArea data={mix} width={60} height={18} />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">47%</span>
  <span className="unit">subscriptions, down from 62%</span>
  <StackedArea data={mix} width={120} height={30} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  Traffic <StackedArea data={mix} height={14} />
</button>
```

## Accessibility

The accessible name is the share-shift read: **"3 series over 12 points; Mobile leads at 66% share."** The interactive
entry announces every layer at once (**"Point 8 of 12: Mobile 56%, Web 36%, API 8%."**), and the readout chip shows that
same breakdown, one row per band.

The interactive entry follows the shared [interaction contract](/docs/accessibility#one-interaction-contract):
arrow keys rove between units on both axes, `Home` and `End` jump to the ends, and a click, tap, `Enter` or
`Space` selects a unit — pinning its readout so it survives blur, until you select it again, press `Escape`, or
press outside the chart. On touch, a tap pins and a drag scrubs.

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `{ label; values }[]` | ≤ 3 series (hard cap). |
| `mode` | `"stacked" \| "ridge"` | Ridge = same stack, overlapping-crest skin. |
| `order` | `"data" \| "asc"` | "asc" puts the smallest series on top (least distortion). |
| `label` | `"last" \| "none"` | Endpoint share labels per series (deterministic drop-out). |
| `labelAt` | `number` | Column whose shares feed label="last" (default: final column). The interactive entry passes the focused column so the labels track the crosshair. |
| `curve` | `"linear" \| "smooth" \| "step"` | Line interpolation (default linear); ridge forces smooth. |
| `colors` | `string[]` | Per-series colours, cycled; overrides --mc-cat-N. |
| `animate` | `boolean` | (interactive) Opt-in entrance motion when the chart mounts client-side — add `import "@microcharts/react/motion"` once. Inert on the server, on hydrated server HTML, and under `prefers-reduced-motion`. |

Plus the shared grammar — `data`, `domain`, `color`, `title`, `summary`, `format` — and the layout props (`width`, `height`, `className`, `style`) that every chart accepts. Interactive entries also share `animate` and `live`, and — wherever a chart has more than one navigable unit — `onActive`, `onSelect`, `selectedIndex` and `defaultSelectedIndex`; and — wherever the chart shows a hover value — `readout`. See [the shared grammar](/docs/quickstart#the-shared-grammar).
