Voice orb
A monochrome sphere of fine contour lines that carries the whole voice state machine through form and motion: idle, connecting, listening, thinking, speaking, interrupted, muted and error.
On this page
No color, no glow. Lines facing you are darker and heavier and the far side shows through faintly, so the sphere reads as volume without a gradient. Each state has its own form, and the direction of the waves says who is talking: inward while the user speaks, outward while the agent replies. It reads with the sound off and in a screenshot.
Installation
Usage
import { VoiceOrb } from "@/components/ui/chatcn/voice-orb"
<VoiceOrb state="listening" level={input.getLevel} />Pass one level, or inputLevel and outputLevel when you meter both sides; the orb uses the input while listening and the output while speaking. Getters are read every frame without re-rendering. For demos without a microphone, simulate drives both sides with a built-in speech rhythm.
Examples
Every state
Sizes
40, 72, 144 and 224 px, or any number. The line count follows the size, from 5 lines at 40 px to 22 at 224 px, so small orbs stay crisp and large ones stay airy.
SVG fallback
Where 2D canvas is missing, or with renderer="svg", the orb draws each state's form in SVG as a still frame. The level scales the whole sphere while listening and speaking.
Reduced motion
With reduced motion on, every state is a still form: the partial trace, the waves, the flattened sphere, the dotted lines, the split. Nothing moves, and every state still reads.
States
| State | Form | Motion |
|---|---|---|
idle | A calm sphere | Breathes every 4.6 s; the surface barely drifts |
connecting | Lines traced in part | The traces chase around the sphere in a spiral |
listening | Waves across the front | Waves travel inward; the level swells them |
thinking | Smaller, turning | A band of light sweeps down every 1.8 s, like the Thinking shimmer |
speaking | Waves across the front | Waves travel outward; the level swells the sphere |
interrupted | Pressed flat | Waves stop |
muted | Dotted, faint | None |
error | Split in two, faint | None |
Props
| Prop | Type | Notes |
|---|---|---|
state | VoiceOrbState | Default idle |
level | number | () => number | 0 to 1. Used for both sides unless split |
inputLevel, outputLevel | number | () => number | Per side, when you meter both |
simulate | boolean | Built-in speech levels wherever no level is passed |
size | "sm" | "md" | "lg" | "xl" | number | 40, 72, 144, 224 px. Default lg |
renderer | "auto" | "canvas" | "svg" | Default auto: canvas, SVG where canvas is missing |
reducedMotion | boolean | Force it on or off. Defaults to the user's setting |
label | string | Accessible name. Defaults to Voice: and the state |
Keyboard
The orb is not interactive on its own. Wrap it in a button to make it one, as VoiceSession does for interrupting.
Accessibility
role="img"with a name that follows the state, for example Voice: listening. Pair it with a visible state label; the orb never carries meaning alone.- Every state differs in form, not only in motion, so it reads under reduced motion and in still screenshots.
- Colors come from the theme through
currentColor, sotext-*classes, dark mode and forced colors apply.
Motion
- Every form parameter eases toward its target with a critically damped approach: no overshoot, no bounce, and any state can interrupt any other.
- Levels use a fast attack and a slower release, so syllables swell and settle instead of flickering.
- The loop pauses off screen and in hidden tabs, and sleeps once the form settles in the still states: interrupted, muted and error cost nothing.
- A frame is a handful of strokes: lines are grouped by depth, so the sphere draws in about ten canvas calls at any size.