Skip to content
VoiceWaveformMenu

Waveform

Live input and output levels as a scrolling history, in bars or one continuous line, or a recorded waveform with its played portion.

Dictation and agent reply strips with scrolling levels. Simulated voices, or your microphone.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/waveform.json

Usage

tsx
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.

Voice memos with a played portion, and the resting baseline.

Props

PropTypeNotes
variant"bars" | "line"Default bars
levelnumber | () => numberLive level from 0 to 1. Getters are read per frame
activebooleanDefault true. False rests on a flat baseline
peaksnumber[]A recorded waveform to show instead of live levels
progressnumberPlayed portion of peaks, from 0 to 1
barWidthnumberDefault 3 for bars; the stroke width (1.5) for line
gapnumberDefault 2
reducedMotionbooleanForce reduced motion on or off
labelstringAccessible 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.