# TraceFold (/docs/charts/trace-fold)

TraceFold answers "where did the latency go — which spans, at which depth, on the path that actually determined the
total?". Each span is a rect placed by its start time, sized by its duration, and stacked by its nesting depth. The
critical path — the chain of spans that bound the total — is accented and everything else muted, so the answer to "which
spans mattered" is immediate.

```tsx
import { TraceFold } from "@microcharts/react/trace-fold";

<TraceFold
  data={[
    { label: "request", start: 0, duration: 214, depth: 0 },
    { label: "db.query", start: 10, duration: 86, depth: 1, parent: 0 },
    { label: "auth", start: 0, duration: 8, depth: 1, parent: 0 },
    { label: "render", start: 96, duration: 60, depth: 1, parent: 0 },
    { label: "serialize", start: 156, duration: 40, depth: 1, parent: 0 },
    { label: "index-scan", start: 12, duration: 70, depth: 2, parent: 1 },
    { label: "decode", start: 82, duration: 12, depth: 2, parent: 1 },
    { label: "log", start: 200, duration: 14, depth: 1, parent: 0 },
    { label: "gc", start: 90, duration: 5, depth: 2, parent: 1 },
  ]}
  format={(n) => `${Math.round(n)} ms\
```

## Install

```tsx
import { TraceFold } from "@microcharts/react/trace-fold";

<TraceFold data={spans} title="Request trace" />
```

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


## When to use it

- **Good for** — request traces, flame charts, p95-exemplar latency breakdowns.
- **Avoid for** — a single duration (Bullet) or a time series (Sparkline).


## Variants

```tsx
<TraceFold
  data={[
    { label: "request", start: 0, duration: 214, depth: 0 },
    { label: "db.query", start: 10, duration: 86, depth: 1, parent: 0 },
    { label: "render", start: 96, duration: 60, depth: 1, parent: 0 },
    { label: "index-scan", start: 12, duration: 70, depth: 2, parent: 1 },
  ]}
  emphasis="none"
  format={(n) => `${Math.round(n)} ms\
```

```tsx
<TraceFold
  data={[
    { label: "request", start: 0, duration: 14868, depth: 0 },
    { label: "db.query", start: 1200, duration: 9234, depth: 1, parent: 0 },
  ]}
  format={{ style: "unit", unit: "millisecond", unitDisplay: "narrow", maximumFractionDigits: 0 }}
  locale="de-DE"
/>
```

`format` also takes `Intl.NumberFormatOptions` — with a `locale`, the accessible summary's numbers follow that locale's
own grouping and decimal marks ("14.868 ms" in German, not "14,868 ms"). In-rect text stays the plain span label either
way; only the announced numbers are localized.

## Edge cases

```tsx
<TraceFold data={[{ label: "request", start: 0, duration: 42, depth: 0 }]} title="Single span" />
```

```tsx
// 3 depths squeezed into a 24px cell — each row falls under the floor
// font's seat height, so labels drop everywhere instead of overlapping
<TraceFold
  data={[
    { label: "request", start: 0, duration: 120, depth: 0 },
    { label: "db.query", start: 10, duration: 70, depth: 1, parent: 0 },
    { label: "index-scan", start: 12, duration: 40, depth: 2, parent: 1 },
  ]}
  height={24}
/>
```


## Why this default

Accenting the critical path because "which spans mattered" is the question a uniform flame strip doesn't answer — the
eye goes straight to the chain that set the total. Widths are durations on one linear shared time scale, never per-row
normalized, so a wide span is genuinely a slow span. The only documented width distortion is a one-unit floor for
zero-duration spans, and the critical-path walk is deterministic (ties resolve to the earliest start).

## Accessibility

The accessible name names the biggest span and its path status — **"4 spans over 214 ms; longest db.query (86 ms) on the
critical path."** The interactive entry roves spans within a depth with ←/→ and between depths with ↑/↓, announcing each
span's duration, share of the total, depth, and whether it is on the critical path.

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 or press `Escape`.
On touch, a tap pins and a drag scrubs.

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `Span[]` | Flat span list; parent = index. |
| `emphasis` | `"critical" \| "none"` | Mute non-critical spans, or uniform. |
| `labels` | `boolean` | Width-gated in-rect labels. |
| `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).
