0

Live Waveform

A canvas-based real-time audio waveform visualizer with microphone input and customizable rendering modes.

"use client"

import * as React from "react"

Installation

bunx --bun shadcn@latest add @ondo-ui/live-waveform

Usage

import { LiveWaveform } from "@/components/ui/live-waveform"
<LiveWaveform active={true} />

The bars default to the primary color. Override with any text-* utility via className, or set barColor for an arbitrary value:

<LiveWaveform active={true} className="text-emerald-500" />
<LiveWaveform active={true} barColor="#3b82f6" />

Examples

Static mode

Symmetric bars across the frequency bands, updated in place. This is the default.

import { LiveWaveform } from "@/components/ui/live-waveform"

export default function LiveWaveformStatic() {

Scrolling mode

Historical average volume scrolling from right to left, so the waveform reads as a timeline. History fills in gradually as audio arrives.

import { LiveWaveform } from "@/components/ui/live-waveform"

export default function LiveWaveformScrolling() {

Processing state

Set processing to show an animated wave while waiting for input — for example between the moment a user stops speaking and the model's reply. It respects prefers-reduced-motion and settles to a calm static frame instead of looping.

"use client"

import * as React from "react"

Bar geometry

barWidth, barGap, barRadius, and barHeight (the minimum bar height) shape the bars — widen them with more gap for a chunky look, set barRadius to half the width for pills or 0 for hard edges. fadeEdges controls whether the ends soften or run to the border.

Start listening to drive every preset from one shared microphone (passed via stream). The processing shape is only the pre-recording state — once listening, every bar stays active and follows the live signal, silence included.

Default
Hairline
Pill
Blocks
Tall & sparse
No fade
"use client"

import * as React from "react"

Custom styling

Bar geometry, color, height, and edge fading are all configurable.

import { LiveWaveform } from "@/components/ui/live-waveform"

export default function LiveWaveformCustom() {

Bring your own stream

Pass a stream to visualize audio you already have — a shared microphone track, or an agent's response audio routed through a MediaStreamAudioDestinationNode. When stream is set, LiveWaveform uses it instead of calling getUserMedia, and leaves the stream's lifecycle to you (it never stops tracks it did not open).

<LiveWaveform active stream={myStream} />

API reference

LiveWaveform

A canvas-based real-time audio visualizer with microphone input support.

PropTypeDefaultDescription
activebooleanfalseWhether to actively listen to audio input.
processingbooleanfalseShow a processing animation when not active.
streamMediaStream | nullnullUse an existing stream instead of the microphone.
deviceIdstringSpecific input device to request from getUserMedia.
barWidthnumber4Width of each bar in pixels.
barHeightnumber4Minimum bar height in pixels.
barGapnumber2Gap between bars in pixels.
barRadiusnumber2Corner radius of bars.
barColorstringBar color. Defaults to the current text color.
fadeEdgesbooleantrueFade the left and right edges of the waveform.
fadeWidthnumber24Width of the fade in pixels.
heightstring | number64Height of the waveform.
sensitivitynumber1Audio sensitivity multiplier.
smoothingTimeConstantnumber0.8Analyser smoothing, 01.
fftSizenumber256FFT size for frequency analysis.
historySizenumber60Bars kept in history (scrolling mode).
updateRatenumber30Audio update interval in milliseconds.
mode"scrolling" | "static""static"Visualization mode.
onError(error: Error) => voidCalled when microphone access fails.
onStreamReady(stream: MediaStream) => voidCalled when the audio stream is ready.
onStreamEnd() => voidCalled when a microphone stream this component owns ends.
classNamestringMerged onto the wrapper element.
...propsHTMLDivElementAll standard div element props.

Notes

  • Uses the Web Audio API for real-time frequency analysis, rendered to a canvas with HiDPI support.
  • Microphone permission is requested when active becomes true; streams and audio contexts are cleaned up on unmount and when active turns off.
  • The render loop parks itself when idle and wakes only for live audio, processing, resizes, and transitions — it does not burn a frame budget at rest.
  • Static mode shows symmetric bars across the frequency bands; scrolling mode shows historical average volume scrolling from right to left.
  • Callbacks are read from a ref, so passing new inline onError / onStreamReady / onStreamEnd functions on each render does not re-acquire the microphone.