VoicePanel
StableA voice conversation (VoiceSession) on the shared AI disc, with every mic, turn and connection state in words, captions, and separate Mute, Hold and End.
Also known as voice mode, voice chat, call screen, voice session
Install with npm install @tattva/ui (setup), or copy the code from the kit in Downloads.
Every state
You: Hi, the kettle I ordered arrived with a cracked lid.
Assistant: Sorry about that. Can you tell me the order number?
You: It's 40-2219.
M muteEsc interrupt
<VoiceSession
state={muted ? "muted" : state}
muted={muted}
onMutedChange={setMuted}
onHold={() => setState("held")}
onResume={() => setState("listening")}
onInterrupt={() => setState("interrupted")}
onEnd={() => setState("ended")}
onMicRetry={askForMic}
onTypeInstead={openChat}
cameraOn={camera}
onCameraChange={setCamera}
workingLabel="Checking your order"
inputLevel={micLevel}
outputLevel={voiceLevel}
elapsed="02:14"
disclosure={<span>Shop assistant · AI · Recorded</span>}
captions={captions}
/>Pick a state, or use the buttons: Mute, Hold then Resume, Interrupt while speaking (or Esc), and End. Keys work while focus is inside the panel.
When to use it
Shows a live voice conversation so that nothing depends on hearing the audio or seeing the animation: the state is in words, the mic state is always visible, and captions carry what was said.
Made of: Disclosure, usage and time · Disc (AIPresence) · State text · Interrupt · Needs-you notice · Camera stopped notice · Footer · Live captions · Talk mode switch · Main control (Mute or Hold to talk) · Hold or Resume · Camera · Share screen · End · Key hints
Use it for
- A spoken conversation with the assistant, hands-free or push-to-talk.
- A support call where the assistant checks something and says so while it works.
- Telling people the mic is blocked, the call is on hold, or the connection is coming back.
- A voice session that may share the camera or the screen.
Not for
- Typing a message: use Composer.
- Showing the assistant is getting ready to reply in text: use TypingIndicator.
- Reviewing a finished call as an operator: use CallRecord.
Do and avoid
Your signal dropped. Stay here, or end the call.
You: Is my parcel still coming today?
Assistant: Yes. It is out for delivery.
You: Is my parcel still coming today?
Assistant: Yes. It is out for delivery.
Do
- Keep captions on by default, and keep the transcript after the call.
- Say what the assistant is doing when it works, such as Checking your order, and show it on screen too.
- Keep Mute, Hold and End as three different things.
- Use the footer to say why it is reconnecting and what people can do.
Avoid
- Do not rely on the disc alone to show the state, and do not give it a face.
- Do not fill silence with fake breaths or filler words. Say One moment, checking.
- Do not use amber for Live or Reconnecting. Amber is only for mic blocked or no mic.
- Do not make End a primary button next to Mute, and do not remove Mute or End to save space.
On a phone
- The panel fills the width up to 32rem and its padding shrinks a step below 640px. Place it in a full-height Sheet for the phone layout.
- The main control is 56px tall. Every other button has a 44px tap area on touch screens.
- End sits on the far right, away from Mute, in a danger outline, so it is not pressed by mistake.
- Camera and Share screen show only their icons below 640px; their names stay for screen readers.
- The disc shrinks from large to medium once captions appear, so captions get the middle of the screen.
- Hold to talk ignores scrolling and the long-press menu, and sliding a finger off it cancels.
- Key hints hide on touch screens.
Seen in products
- ChatGPT (November 2025) moved voice inline into the chat with a live transcript, visuals and an End button.
- ChatGPT keeps the old orb screen with a blue circle, mute, video and X under Settings, Voice Mode, Separate mode.
- Gemini Live has Mute, Hold and End as three separate states, and goes on hold when the phone locks if Gemini on lock screen is off.
- Gemini Live supports camera and screen sharing; camera and screen stop on lock and need a manual restart.
- The ElevenLabs agents widget has configurable listening and speaking text, so the state is always written.
- Claude Code's /voice uses Space held as push-to-talk.
- Vercel AI Elements ships Voice components: Audio Player, Mic Selector, Persona, Speech Input, Transcription and Voice Selector.
- TechCrunch: ChatGPT's voice mode is no longer a separate interface (opens in a new tab)
- Google Gemini Help: Talk naturally with Gemini Live (opens in a new tab)
- Google: Gemini Live updates, August 2025 (opens in a new tab)
- ElevenLabs: Agents widget (opens in a new tab)
- Vercel AI Elements (opens in a new tab)
Properties
| Prop | Type | Default | Description |
|---|---|---|---|
| staterequired | "connecting" | "ready" | "listening" | "userSpeaking" | "thinking" | "working" | "speaking" | "interrupted" | "muted" | "held" | "reconnecting" | "micBlocked" | "noMic" | "ended" | "error" | none | Current state. Drives the disc, the words and which controls show. |
| stateLabels | Partial<Record<VoiceState, string>> | none | Overrides the visible text for any state. |
| inputMode | "handsFree" | "pushToTalk" | "handsFree" | Push-to-talk turns the main control into Hold to talk. |
| onInputModeChange | (mode: "handsFree" | "pushToTalk") => void | none | Shows a Hands-free / Push to talk switch when given. |
| onTalkStart | () => void | none | Push-to-talk: the person started holding. |
| onTalkEnd | (cancelled: boolean) => void | none | Push-to-talk: the person let go. cancelled is true when they slid off first. |
| captions | VoiceCaption[] | ReactNode | [] | Simple captions (id, speaker, text), or your own node such as LiveCaptions. |
| speakerLabels | { you: string; assistant: string } | You, Assistant | Names shown before each simple caption. |
| emptyCaptions | string | none | Text shown when there are no captions. |
| muted | boolean | none | Pressed state of Mute. Defaults to true when state is muted. |
| onMutedChange | (muted: boolean) => void | none | Called by Mute and by the M key. |
| onHold | () => void | none | Shows Hold. Nothing is heard or said until Resume. |
| onResume | () => void | none | Called by Resume in the held state. |
| onEnd | () => void | none | Called by End. End has no key. |
| onInterrupt | () => void | none | Shows Interrupt while speaking, and makes Esc stop the assistant. |
| inputLevel | number | none | Mic level 0 to 1. Moves the disc while listening and fills the neutral level bars on Mute. |
| outputLevel | number | none | Assistant level 0 to 1. Moves the disc while speaking. Ignored under reduced motion. |
| workingLabel | string | none | Text for the working state, such as Checking your calendar. |
| onMicRetry | () => void | none | Allow microphone (blocked) or Check again (no mic). |
| onTypeInstead | () => void | none | Type instead, in the blocked and no mic states. |
| media | { camera?: "on" | "off" | "stoppedBySystem"; screen?: "on" | "off" | "stoppedBySystem" } | none | Camera and screen state. stoppedBySystem shows a notice with a button to turn it on again. |
| cameraOn | boolean | none | Pressed state of Camera, when you do not pass media. |
| onCameraChange | (on: boolean) => void | none | Shows the Camera toggle when provided. |
| screenSharing | boolean | none | Pressed state of Share screen, when you do not pass media. |
| onScreenChange | (on: boolean) => void | none | Shows the Share screen toggle when provided. onScreenShareChange is the older name. |
| elapsed | string | none | Time since the start, such as 02:14. Shown, never announced. |
| usage | ReactNode | none | Minutes or credits used, such as a compact UsageMeter. |
| disclosure | ReactNode | none | Who is talking and whether it is recorded. |
| footer | ReactNode | none | Content under the state, such as a connection hint. |
| showKeyHints | boolean | true | Shows Esc, M and Space hints on screens with a fine pointer. |
| className | string | none | Extra classes for the panel. |
"Show code under the preview gives the code for the example on the stage."
In Figma
In Figma, one VoicePanel switches all of this from its properties panel, across 60 variants. Each property sets the React prop beside it, so design and code use the same names.
| Figma property | React | Values |
|---|---|---|
| Status | state | Connecting, Ready, Listening, UserSpeaking, Thinking, Working, Speaking, Interrupted, Muted, Held, Reconnecting, MicBlocked, NoMic, Ended, Error |
| Muted | muted | On or off |
| Camera on | cameraOn | On or off |
| Elapsed | elapsed | Any text |
| WorkingLabel | workingLabel | Any text |
Tokens
Change one of these and every part that uses it follows.
--lime--surface--surface-sunken--border--border-strong--warning-soft--warning-fg--danger--danger-fg--danger-soft--radius-card--dur-fast--dur-base--dur-slow
Built in
- There is one polite status region (the disc's). It announces each state change once, never caption words.
- The disc is hidden from screen readers. The words and the captions carry the information.
- Simple captions are a role="log" that can be reached by keyboard and scrolled. It does not read new captions out.
- Mute, Camera, Share screen and Hold to talk report whether they are on with aria-pressed, and their names stay the same.
- Keys work while focus is inside the panel and never while typing in a field: Space held to talk, M to mute, Esc to interrupt. End has no key.
- The person's own mic level is three neutral bars, never lime. Lime is the AI disc only.
- When people turn off motion in their system settings, the disc is drawn still for each state and the state text stays.
What you need to do
- Keep the captions filled from your speech service, so people who cannot hear can still follow.
- Pass muted, and the camera and screen state, from your own state, so each button reports whether it is on.
- Stop the microphone and the call in onEnd. Stop the assistant's audio within a moment in onInterrupt.
- In micBlocked and noMic, give onTypeInstead a real text route. Every voice flow needs one.
- Never restart the camera or screen sharing yourself after a lock. Wait for the person to press Turn camera on.
- When a screen reader is running, offer push-to-talk so the assistant does not hear the reader.
Built toward WCAG 2.2 AA and checked automatically; no independent audit yet. Read the accessibility guide