Waveform
StableThe loudness of a clip drawn as bars, with the played part filled. Can also be the seek control.
Also known as audio waveform, sound wave, visualizer
Install with npm install @tattva/ui (setup), or copy the code from the kit in Downloads.
Seek control with markers
At 0:52 of 3:12. One marker needs review.
const [pos, setPos] = useState(52);
<Waveform
label="Seek, call with Maya Okafor"
peaks={peaks}
duration={192}
position={pos}
onSeek={setPos}
markers={[
{ at: 41, label: "Tool call: look up order 4471" },
{ at: 96, label: "Caller interrupted the assistant" },
{ at: 158, label: "Transfer to a person, check the reason", tone: "review" },
]}
/>A call recording. Drag, tap or use the arrow keys to seek. Markers show a tool call, an interruption and, as an amber diamond, a transfer someone should review.
When to use it
Shows the shape of a clip so people can see where the talking, the quiet parts and the marked moments are, and jump straight to them.
Made of: Bars · Played part · Playhead · Regions · Markers · Still-being-made track · Region key with times · Time bubble while dragging
Use it for
- The seek bar of a call recording, a voice clip or a song.
- Showing which stretch of a song or voice-over the AI made or changed.
- A small level display beside a microphone, with its state written next to it.
Not for
- A full player with play, time and speed: use AudioPlayer.
- Picking a value on a scale: use Slider.
- A trend in numbers over time: use Sparkline.
Do and avoid
Do
- Precompute the peaks on the server, 100 to 200 values for a clip, so the page does not decode the audio.
- Pass the real media time as position, and let the playhead move with it.
- Label every region and marker in plain words.
- Use the ai region only for stretches the AI made or changed.
Avoid
- Don't colour the bars, the playhead or the played part with the AI colour. Playback is not AI.
- Don't use amber for anything but a stretch or moment a person must act on.
- Don't animate the bars to the beat as the main sign of state. Write the state in words.
- Don't make the live drawing the only sign that the microphone is on.
On a phone
- It fills the width. As a seek control its hit area is at least 44px tall, even at the 32px height.
- Tap anywhere to seek. While a finger drags, a time bubble shows above it.
- A touch that starts on it does not scroll the page, so dragging stays put.
- When the waveform is narrower than 256px (a 320px phone inside a card) the bars give way to a plain progress bar.
Seen in products
- Vapi call logs put a waveform you can play and scrub at the top of each call.
- ElevenLabs Studio describes its waveforms as showing relative loudness.
- Suno's Song Editor works directly from the waveform: select a stretch, then Replace or Extend.
- Descript makes the selected text pulse while it regenerates that part.
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
| peaksrequired | number[] | null | none | Loudness per slice, each 0 to 1. null while still being worked out, which shows the drawing state. In live mode the newest level is last. |
| durationrequired | number | none | Length of the clip in seconds. In live mode, the seconds the visible window covers. |
| position | number | 0 | Playhead in seconds. Bars before it are drawn as played. |
| onSeek | (seconds: number) => void | none | Makes it a seek slider. Called on tap, drag and key presses. Leave out for a drawing only. |
| label | string | none | Name of the seek slider, such as "Seek, call with Maya Okafor". Needed with onSeek. |
| regions | { start: number; end: number; kind: "ai" | "person" | "stale" | "selected" | "highlight"; label?: string }[] | none | Marked stretches. ai gets the AI wash and a dotted top edge, stale an amber hatch, selected an ink outline, highlight a neutral wash, person a solid top edge. |
| markers | { at: number; label: string; tone?: "neutral" | "review" }[] | none | Moments on the timeline. review is an amber diamond, for a moment a person must check. |
| pendingFrom | number | none | From this second to the end the clip is still being made. Drawn faded behind a dashed line. |
| mode | "static" | "live" | "static" | live draws the most recent levels, newest on the right, with no playhead or seeking. |
| liveBars | number | 64 | In live mode, how many bars to show. |
| error | string | none | Shows a plain bar and this message instead of the peaks. Seeking still works. |
| simple | boolean | false | Draws a plain progress bar instead of the peaks. |
| height | 32 | 48 | 72 | 48 | Height of the drawing. The seek hit area is at least 44px tall. |
| formatTime | (seconds: number) => string | none | Clock text for the time bubble while dragging. Defaults to formatMediaTime, such as 1:12. |
| className | string | none | Extra classes on the wrapper. |
"Show code under the preview gives the code for the example on the stage."
In Figma
In Figma, one Waveform switches all of this from its properties panel, across 4 variants. Each property sets the React prop beside it, so design and code use the same names.
| Figma property | React | Values |
|---|---|---|
| State | Hover, Pressed and Focus come from the browser. | Default, Hover, Pressed, Focus |
Tokens
Change one of these and every part that uses it follows.
--fg--fg-muted--fg-subtle--border-strong--surface--lime--attention--dur-base--text-small
Built in
- With onSeek it is one element with role slider, aria-valuemin, aria-valuemax and aria-valuenow in seconds, and aria-valuetext in words, such as "1 minute 12 seconds of 3 minutes 12 seconds".
- Arrow keys move 5 seconds, Shift with an arrow 15 seconds, Page Up and Page Down 30 seconds, Home and End go to the start and end.
- Regions, markers, the drawing state, the error and the still-being-made part are read out as the slider's description.
- Without onSeek, or in live mode, the drawing is hidden from screen readers. Region labels stay readable as text.
- Played and unplayed bars differ in lightness, and the playhead is a line, so the played part does not depend on hue. AI regions add a dotted edge and words; stale regions a hatch and words; review markers a diamond shape.
- A press focuses the slider, and drags use pointer capture so they keep working outside it. The hit area is at least 44px tall.
- Under reduced motion the peaks do not fade in, the playhead moves at most four times a second, and a live drawing changes at most once a second.
- In forced colours the playhead, played part and selection keep system colours.
What you need to do
- With onSeek, give it a label that names the clip, such as "Seek, call with Maya Okafor".
- In live mode or without onSeek it is hidden from screen readers, so write the state (such as "You, speaking") beside it.
- Give each region and marker a label in plain words. They are read out with the slider.
- Update position from the real media time. Do not ease or animate it.
Built toward WCAG 2.2 AA and checked automatically; no independent audit yet. Read the accessibility guide