Voice Visualizer
The agent-presence indicator for a voice UI — dots (or bars) that move with the agent's state and audio level. It's the “is this thing on?” signal, so it never goes dead.
Cycle through connecting → listening → thinking → speaking.
tsx
"use client"
import * as React from "react"
import { Button } from "@/registry/seam/ui/button"
import { VoiceVisualizer } from "@/registry/seam/ui/voice-visualizer"
const STATES = ["connecting", "listening", "thinking", "speaking"] as const
export default function VoiceVisualizerDemo() {
const [i, setI] = React.useState(1)
const state = STATES[i]
// "listening"/"speaking" react to a live level; fake one when there's no mic.
const [level, setLevel] = React.useState(0)
React.useEffect(() => {
if (state !== "listening" && state !== "speaking") return
const id = setInterval(() => setLevel(0.2 + Math.abs(Math.sin(performance.now() / 300)) * 0.8), 80)
return () => clearInterval(id)
}, [state])
return (
<div className="flex flex-col items-center gap-5">
<VoiceVisualizer state={state} level={level} size="lg" />
<Button
variant="secondary"
size="sm"
onClick={() => setI((n) => (n + 1) % STATES.length)}
>
State: {state}
</Button>
</div>
)
}bunx --bun seamui@latest add voice-visualizerAPI
| Prop | Type | Default | Description |
|---|---|---|---|
| state | "disconnected" | "connecting" | "listening" | "thinking" | "speaking" | "listening" | Drives the motion and the accessible label. |
| level | number | — | Audio level 0–1; takes precedence over track analysis. |
| track | MediaStreamTrack | null | — | Analysed via Web Audio by the owned useAudioLevel hook. |
| count | number | 5 | Number of dots or bars. |
| size | "sm" | "default" | "lg" | "default" | Dot/bar dimensions. |
| variant | "dots" | "bars" | "dots" | Dots scale uniformly; bars stretch vertically. |
Notes
- Each state has its own motion: connecting is a staggered opacity shimmer, listening breathes with the mic level (spring-smoothed on
springs.snappyso raw analyser values never jitter the UI), thinking is a sequential opacity sweep, and speaking scales with the agent's output level, weighted so the center moves most. - Every channel maps to opacity under reduced motion — a call UI's liveness signal must never freeze.
- Transport-agnostic: pass a numeric
levelor atrackfor the owneduseAudioLevelhook. The prop shape maps 1:1 onto the AI SDK / LiveKituseVoiceAssistanthooks with no runtime dependency. - A
role="status"labelled from the state (“Agent is listening”); the dots arearia-hidden, andVoiceVisualizerCaptionis the visible equivalent of that label.
Press feedback, reduced motion, and haptics follow the global policy — see Motion and Haptics.