Waveform
Live input and output levels as a scrolling history, in bars or one continuous line, or a recorded waveform with its played portion.
On this page
Installation
Usage
import { Waveform } from "@/components/ui/chatcn/waveform"
import { useChatcnAudioLevel } from "@/hooks/use-chatcn-audio-level"
const mic = useChatcnMicrophone()
const meter = useChatcnAudioLevel(mic.stream, { updateRate: 0 })
<Waveform level={meter.getLevel} active={mic.status === "active"} className="h-7" />Pass level as a getter, such as getLevel from useChatcnAudioLevel, and the waveform reads it once per frame without re-rendering. A plain number works too. Height comes from className (default h-8); width fills the parent.
Examples
Live
Each bar is a moment, sixteen a second. The newest lands at the end and the history slides toward the start, fading out instead of being cut off. Bars hold the loudest reading of their moment, so syllables land crisply. The line variant swings once per moment for the agent's reply.
Recorded
Pass peaks (any length, any scale) for a voice memo or a read-aloud clip. progress from 0 to 1 inks the played portion and splits mid-bar where playback is. active={false} rests on a flat row of dots.
Props
| Prop | Type | Notes |
|---|---|---|
variant | "bars" | "line" | Default bars |
level | number | () => number | Live level from 0 to 1. Getters are read per frame |
active | boolean | Default true. False rests on a flat baseline |
peaks | number[] | A recorded waveform to show instead of live levels |
progress | number | Played portion of peaks, from 0 to 1 |
barWidth | number | Default 3 for bars; the stroke width (1.5) for line |
gap | number | Default 2 |
reducedMotion | boolean | Force reduced motion on or off |
label | string | Accessible name. Defaults to Input level, Idle or the played percent |
Keyboard
None. The waveform is a picture of sound; put playback and recording controls next to it.
Accessibility
- It is an image with a name: Input level, Idle, or Waveform with the percent played, so a recorded clip says where playback is.
- Pair it with a visible timer and a labelled stop or play button. The bars alone never carry the state.
- Colors come from the theme and
text-*classes, so it follows dark mode and forced colors.
Motion
- The history scrolls continuously at 80 px a second with sub-pixel steps, so it glides instead of ticking.
- Under reduced motion nothing travels: the bars hold their places and show the current level as a centred hump, about eight updates a second, without easing.
- The loop sleeps when the waveform is inactive, recorded, off screen or in a hidden tab.