# Waveform (/docs/charts/waveform)

Waveform buckets a high-frequency signal and draws each bucket's maximum, never its mean, so one spike survives the
squeeze down to sparkline width. Averaging would erase the spike someone is looking for. The auto domain is symmetric at
±max|data|, and the peak is disclosed in the accessible summary.

```tsx
import { Waveform } from "@microcharts/react/waveform";

<Waveform
  data={Array.from(
    { length: 200 },
    (_, i) =>
      (i === 126 ? 0.82 : Math.sin(i / 3) * 0.15 + Math.sin(i / 11) * 0.35) *
      (1 - Math.abs(i - 100) / 260),
  )}
  title="Voice memo"
/>
```

## Install

```tsx
import { Waveform } from "@microcharts/react/waveform";

<Waveform data={samples} title="Voice memo" />
```

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 { Waveform } from "@microcharts/react/waveform/interactive";

<Waveform
  data={samples}
  progress={0.63}
/>
```

## When to use it

Use it for voice-memo and audio scrubbers, and for high-frequency log or request volume. For exact values use Sparkline;
for categorical state use Hypnogram.

## Sizing

**table cell**

```tsx
<Waveform data={row.samples} width={60} height={14} />
```

**envelope**

```tsx
<Waveform data={samples} mode="envelope" />
```

## Variants

```tsx
<Waveform
  data={Array.from({ length: 200 }, (_, i) => Math.sin(i / 3) * 0.4 * (1 - Math.abs(i - 100) / 220))}
  mode="envelope"
/>
```

```tsx
<Waveform data={[0.2, 0.5, 0.82, 0.4, 0.1]} locale="de-DE" />
```

With a `locale`, the announced peak follows that locale's own decimal mark: "0,82" in German, not "0.82".

Comparing loudness across rows needs an explicit shared `domain`. Without one, each chart fits its own max, and quiet
data is rescaled to look loud.

## Edge cases

```tsx
<Waveform data={[0.6]} />
```

```tsx
<Waveform data={[0, 0, 0, 0, 0, 0, 0, 0]} />
```

A single sample renders as one bucket, top-to-bottom. All-zero data renders every bucket at the shared 0.4-unit "silent"
tick height, and the accessible summary says **"Silent."** rather than reporting a zero peak. A `null` behaves the same
as a true zero for any bucket where it is the only sample: `maxPerBucket` skips non-finite values when picking the
bucket's peak, and a bucket with nothing finite in it falls back to that same silence tick. A genuine gap and real
silence therefore render identically here, unlike Sparkline's line break.

## Four homes

**In a sentence**

```tsx
<p>
  Voice memo amplitude{" "}
  <span className="mc-inline">
    <Waveform data={samples} progress={0.63} width={90} height={16} summary={false} />
  </span>{" "}
  — peak 0.82 at 63%.
</p>
```

**In a table cell**

```tsx
<td>
  <Waveform data={row.samples} width={72} height={16} />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">0.82</span>
  <span className="unit">peak · 63% through</span>
  <Waveform data={samples} progress={0.63} width={200} height={30} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  Standup memo <Waveform data={samples} width={54} height={14} />
</button>
```

## Accessibility

The accessible name discloses the peak: **"Peak 0.398 at 50% through 200 samples."** Pure silence reads **"Silent."**
rather than blank. The interactive entry roves the buckets, announcing each bucket's position and peak amplitude.

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) | `number[]` | Amplitude samples; negatives allowed. |
| `progress` | `number` | 0–1 played fraction; left buckets tint accent. |
| `mode` | `"bars" \| "envelope"` | Envelope draws the min/max area. |
| `mirror` | `boolean` | Mirror around center; false for magnitude-only. |
| `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).
