Skip to content
Sign in
VoiceVoice widgetMenu

Voice widgetPro

A floating call widget for websites. The launcher opens a small panel that asks for consent once, then runs the call with the orb, rolling captions, mute and End, and the full transcript a tap away. Minimize mid call and the launcher carries it.

ProA clinic site with the widget in the corner. Agree once and the call runs in the panel; minimize it and the launcher carries the call.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/voice-widget

Usage

tsx
import { VoiceWidget } from "@/components/ui/chatcn/pro/voice-widget"

<VoiceWidget
  agentName="Fernhill Vets"
  state={voice.state}
  segments={voice.segments}
  inputLevel={mic.getLevel}
  outputLevel={agent.getLevel}
  startedAt={voice.startedAt}
  onStart={voice.start}
  onEnd={voice.end}
/>

Drop it anywhere on a page: position="fixed" floats it in the corner, contained floats it inside its positioned parent. The consent step shows before the first call only; pass your own copy to consent, a privacyHref, or false to skip it. Below 480 px of host width the panel becomes a sheet across the bottom. Any orb with the voice orb's props fills the panel through orb.

Examples

States

ProConsent, ready, connecting, speaking, transcript, muted, blocked mic, dropped, ended, minimized and a pixel orb.

Props

PropTypeNotes
state"idle" | "connecting" | "listening" | "thinking" | "speaking" | "interrupted" | "muted" | "reconnecting" | "error" | "ended"The call
segmentsTranscriptSegment[]Feeds the captions and the transcript
inputLevel, outputLevelnumber | () => numberA getter animates without re-rendering
startedAt, endedAtnumberEpoch ms, for the timer
agentName, agentDetailstring, ReactNodeHeader, launcher and captions; a line under the orb before a call
userLabelstringDefault "You"
launcherLabelstringDefault "Talk to us"
open, defaultOpen, onOpenChangebooleanThe panel
consentReactNode | falseShown once before the first call. Default a recording and transcription notice
privacyHrefstringAdds a privacy policy link to the consent step
consented, defaultConsented, onConsentedChangebooleanPersist it so returning visitors skip the step
view, defaultView, onViewChange"call" | "transcript"Orb and captions, or the full transcript
muted, defaultMuted, onMutedChangebooleanControlled or uncontrolled
onStart, onEnd, onReconnect() => voidCall actions
onInterrupt() => voidTap the orb while the agent speaks to cut it off
onSendTranscript() => voidAdds "Email transcript" after the call
micError, onRetryMicrophoneChatcnMicrophoneErrorThe body says how to fix it and offers Try again
errorMessageReactNodeShown when the call drops. Say how to fix it
orbComponentTypeAny orb with the voice orb's props. Default VoiceOrb
side"end" | "start"The corner, logical. Default end
position"fixed" | "contained"Default fixed

Keyboard

KeyAction
Enter, Space on the launcherOpen the panel and move focus to its main action
EscapeMinimize. The call keeps going and focus returns to the launcher
MMute or unmute, while focus is in the panel and not in a text field
TabMoves through the panel; the closed panel is inert

Accessibility

  • The panel is a non modal dialog named by the agent, so the page stays usable during a call. Closed, it is inert and invisible to assistive tech.
  • The launcher has aria-expanded and reads its visible text: "Talk to us", or "On call 1:12" mid call, with End as its own labelled button.
  • State changes are announced politely; the ticking timer never is. Captions do not announce, the transcript does, so lines are read once.
  • Every error says how to fix it: a blocked microphone gives the exact place to allow it, a dropped call offers Reconnect and keeps the transcript.

Motion

  • The panel opens from the launcher's corner: opacity, scale from 0.96 and 8 px of rise over 220 ms, out in 160 ms. Opened or closed from the keyboard, it is instant.
  • Views change with a 180 ms rise and fade. The launcher's level bars follow the voice on the frame clock and rest under reduced motion, where the panel only fades.