Waveform

8 parts

A headless audio waveform: it renders amplitude peaks as bars (or a smoothed path), overlays a draggable playback cursor, and hosts zero or more draggable regions (selections). The root owns the peaks, the currentTime model, and the regions model; it measures its own width, builds a time↔pixel projection over the visible window (offset + zoom for timeline sync), and resamples peaks to the available bar count by ratio (never assuming one peak per pixel). Pointer-down on the body scrubs the cursor; with createRegionOnDrag a press-drag marquees out a new region. It provides context to WaveformBars/WaveformPath, WaveformCursor, WaveformRegion, WaveformSelectionPreview, and WaveformEmpty. Reach for it to visualize and navigate audio (players, trimmers, transcript editors).

Demo

Loading demo…

Anatomy

Import the parts and compose them. Each part forwards attributes to its underlying element.

vue
import {
  WaveformRoot,
  WaveformBars,
  WaveformCursor,
  WaveformEmpty,
  WaveformPath,
  WaveformRegion,
  WaveformRegionHandle,
  WaveformSelectionPreview,
} from '@robonen/primitives/canvas/waveform';

<WaveformRoot>
  <WaveformBars />
  <WaveformCursor />
  <WaveformEmpty />
  <WaveformPath />
  <WaveformRegion />
  <WaveformRegionHandle />
  <WaveformSelectionPreview />
</WaveformRoot>

API Reference

WaveformRoot

Root

A headless audio waveform: it renders amplitude peaks as bars (or a smoothed path), overlays a draggable playback cursor, and hosts zero or more draggable regions (selections). The root owns the peaks, the `currentTime` model, and the `regions` model; it measures its own width, builds a time↔pixel projection over the visible window (`offset` + `zoom` for timeline sync), and resamples `peaks` to the available bar count by ratio (never assuming one peak per pixel). Pointer-down on the body scrubs the cursor; with `createRegionOnDrag` a press-drag marquees out a new region. It provides context to `WaveformBars`/`WaveformPath`, `WaveformCursor`, `WaveformRegion`, `WaveformSelectionPreview`, and `WaveformEmpty`. Reach for it to visualize and navigate audio (players, trimmers, transcript editors).

PropTypeDescription
peaks?number[] | Float32ArrayPer-sample amplitudes. Either normalized 0..1 magnitudes or signed -1..1 PCM-style samples — select which via peaksRange. Length is decoupled from duration: the root resamples by ratio to the bar count.
peaksRange?WaveformPeaksRangeThe amplitude convention of peaks. '-1..1' (signed) is rectified via absolute value; '0..1' is passed through.
duration?numberTotal media duration, in seconds.
currentTime?numberCurrent playback position in seconds (v-model:current-time).
regions?WaveformRegionData[]The set of regions (v-model:regions).
offset?numberLeft edge of the visible window, in seconds (for timeline sync).
zoom?numberHorizontal zoom in pixels-per-second. 0 means "fit": the whole [offset, duration] span fills the measured width.
barWidth?numberBar thickness in pixels (bars mode).
barGap?numberGap between bars in pixels (bars mode).
mode?WaveformModeBody render strategy.
createRegionOnDrag?booleanA press-drag on the body marquees out a new region instead of seeking.
step?numberKeyboard step for the cursor / region edges, in seconds. 0 means "1 pixel of time" — resolved from the current projection.
largeStep?numberLarge keyboard step (Shift+Arrow), in seconds.
timeFormatter?WaveformTimeFormatterDefault formatter for the cursor's aria-valuetext.
loading?booleanMark the waveform as loading async peaks (sets data-loading).
disabled?booleanDisable all interaction.
dir?WaveformDirectionWriting direction. When omitted it is inherited from the nearest ConfigProvider (falling back to 'ltr'); an explicit value wins.
Emits
EventPayload
update:currentTime[value: number]
update:regions[value: WaveformRegionData[]]

WaveformBars

Bars

Renders the resampled bar geometry from `WaveformRoot` as a row of elements, one per bucket, each positioned and sized from the computed `buckets`. Purely presentational (`role="presentation"`, `aria-hidden`) — the accessible model lives on `WaveformCursor` and `WaveformRegion`. Bar heights are exposed as a `0..1` fraction (via CSS custom prop `--waveform-bar`) and as an explicit height percentage so consumers can style freely. Render this OR `WaveformPath`, per the root's `mode`.

No props or events — renders its element and forwards attributes.

WaveformCursor

Cursor

The playback cursor, rendered as `role="slider"` over `[0, duration]` with `aria-valuenow` = the current time. It positions itself at `projection(currentTime)` and is fully keyboard-driven: Arrow Left/Right scrub by `step` seconds (Shift by `largeStep`), Home/End jump to `0`/`duration`, PageUp/PageDown seek by one visible window. Give it an `aria-label` (defaults to "Playback position"). `aria-valuetext` comes from `timeFormatter` (falling back to the root's, then `formatClock`).

PropTypeDescription
timeFormatter?WaveformTimeFormatterFormatter for aria-valuetext. Overrides the root's formatter for this cursor only.

WaveformEmpty

Empty

Placeholder shown when there is nothing to render — `duration === 0` or the peaks array is empty (the same condition that puts `data-empty` on the root), or while `loading` async peaks. Renders only in those states; the default slot receives `isEmpty` / `loading` so a consumer can show a spinner vs. a "no audio" message. Use this OR rely on the root's `data-empty` / `data-loading`.

PropTypeDescription
whileLoading?booleanAlso render while the root is loading. When false, only renders for the empty (no peaks / zero duration) state.

WaveformPath

Path

Alternative SVG rendering of the waveform: a single smoothed `<path>` through the resampled peaks (mirrored top/bottom for a filled silhouette). Smoothing is Catmull-Rom (`buildSmoothPath`) at the supplied `tension`. Purely presentational (`aria-hidden`). Render this OR `WaveformBars`, per the root's `mode`. The `<svg>` stretches to the part's box; size it via CSS.

PropTypeDescription
samples?numberNumber of points sampled across the body. Higher is smoother but costlier.
tension?numberCatmull-Rom tension forwarded to buildSmoothPath (0 = uniform). Higher flattens the curve.
filled?booleanFill the area under the silhouette (mirrored) instead of stroking a line.

WaveformRegion

Region

A single audio region (selection), rendered as `role="group"` whose `aria-label` is the formatted `start–end` range (plus the region `label` when set). It positions and sizes itself from `projection(start)..projection(end)` and hosts two `WaveformRegionHandle` children (one per edge) so trimming is keyboard-driven like a two-thumb range slider. The group itself can be moved by dragging its body; Enter/Space select it, Delete/Backspace remove it. Exposes `data-selected` / `data-active`.

PropTypeDescription
regionIdstringThe id of the region (in the root's regions) this part renders.
draggable?booleanWhether the group body can be dragged to move the whole region.

WaveformRegionHandle

RegionHandle

One trim handle of a `WaveformRegion`, rendered as `role="slider"` over `[0, duration]` with `aria-valuenow` = its edge's time. Pass `edge="start"` or `edge="end"`. Dragging trims that edge; Arrow Left/Right move it by `step` (Shift by `largeStep`), Home/End jump the edge to `0` / `duration`. Together the two handles behave like a two-thumb range slider over the region. Default `aria-label` is "Region start" / "Region end".

PropTypeDescription
edgeWaveformRegionEdgeWhich edge of the parent region this handle trims.

WaveformSelectionPreview

SelectionPreview

The transient marquee shown while a new region is being dragged out (`createRegionOnDrag`). Renders nothing until a create gesture is in flight, then positions/sizes itself from the in-progress selection. The pixel geometry is also exposed via the default slot so a consumer can render their own preview surface. `aria-hidden` (it has no committed value yet).

No props or events — renders its element and forwards attributes.