# BenchmarkStrip (/docs/charts/benchmark-strip)

BenchmarkStrip answers "is this value normal for its peer group?". A focal dot sits on a common scale against the peers'
own **empirical** quantile bands — never a fitted distribution. There is no axis: the band is the reference frame. Small
samples fall back to min–max so tail quantiles are never fiction.

```tsx
import { BenchmarkStrip } from "@microcharts/react/benchmark-strip";

// 42 peer latencies (ms) const peerLatencies = [ 180, 201, 237, 286, 341, 396, 443, 394, 412, 413, 398, 372, 340, 310,
205, 196, 205, 230, 271, 322, 378, 347, 391, 421, 434, 430, 412, 383, 268, 239, 221, 218, 231, 261, 306, 275, 331, 382,
422, 447, 455, 447, ];

<BenchmarkStrip data={peerLatencies} value={312} title="Latency vs peers" />
```

## Install

```tsx
import { BenchmarkStrip } from "@microcharts/react/benchmark-strip";

<BenchmarkStrip data={peerLatencies} value={312} format={{ style: "unit", unit: "millisecond" }} title="Latency vs peers" />
```

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** — a value against its cohort, per-row peer comparison in tables, SLA context.
- **Avoid for** — a single trend (Sparkline) or two groups head-to-head (ABStrips).


## Variants

```tsx
<BenchmarkStrip data={[210, 260, 300, 340, 410]} value={300} range="minmax" />

// 42 peer latencies (ms) const peerLatencies = [ 180, 201, 237, 286, 341, 396, 443, 394, 412, 413, 398, 372, 340, 310,
205, 196, 205, 230, 271, 322, 378, 347, 391, 421, 434, 430, 412, 383, 268, 239, 221, 218, 231, 261, 306, 275, 331, 382,
422, 447, 455, 447, ];

<BenchmarkStrip data={peerLatencies} value={312} label="value" />
```

```tsx
<BenchmarkStrip
  data={[1080, 1180, 1220, 1360, 1440]}
  value={1240}
  label="value"
  format={{ maximumFractionDigits: 0 }}
  locale="de-DE"
/>
```

`format` also takes `Intl.NumberFormatOptions` — with a `locale`, both the right-gutter label and the accessible
summary's numbers follow that locale's own grouping and decimal marks: the value above reads "1.240" in German, not
"1,240".

## Edge cases

```tsx
// tail quantiles (p5/p95) would be fiction below 8 points — the strip
// switches to min–max automatically, even with the default range prop
<BenchmarkStrip data={[210, 260, 300, 340, 410]} value={300} />
```

```tsx
<BenchmarkStrip data={[50, 50, 50, 50, 50, 50, 50, 50]} value={52} />
```

```tsx
// with an explicit domain, a value beyond it clamps to the edge and draws
// a small directional wedge instead of running off the strip
<BenchmarkStrip
  data={[180, 250, 300, 350, 420]}
  value={520}
  domain={[150, 400]}
/>
```


## Why this default

Bands come from the peers' own empirical quantiles, never a fitted distribution — the strip never implies a normal curve
that the data doesn't back up. There is no axis: the band itself is the reference frame, so the focal dot's position
against it is the whole read. Below 8 peers, tail quantiles (p5/p95) are statistically unreliable, so the outer band
falls back to the observed min–max automatically — a deliberate honesty trade the `range` prop can also force at any
sample size. A value that falls outside the plotted domain clamps to the edge and draws a small wedge rather than
silently disappearing.

## Accessibility

The accessible name states the value, its percentile, the peer count, and the middle-half interval — **"312 — 43rd
percentile of 42 peers (middle half 244.5–408.5)."** A flat cohort states it plainly instead: "52 — all 8 peers at 50."
The interactive entry roves the five quantile edges with ←/→, announcing each edge's name and value (e.g. "p75:
408.5.").

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) | `number[]` | Peer values. |
| `value` (required) | `number` | The focal reading. |
| `range` | `"p5p95" \| "minmax"` | Outer band; minmax for small samples. |
| `label` | `"value" \| "percentile" \| "none"` | What the right gutter states (default percentile). |
| `positive` | `"up" \| "down"` | Which side of the band is good (colors the focal dot). |
| `median` | `boolean` | Center tick (default true). |
| `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).
