useThunderPhone hook gives you complete control over the user interface while ThunderPhone manages the voice session, audio routing, and connection state. Use it when you want a fully custom UI — your own buttons, layouts, animations, and branding — while ThunderPhone handles everything under the hood.
When to Use the Headless Hook
The pre-builtThunderPhoneWidget component covers most use cases, but reach for the headless hook when you need:
- A completely custom call UI that matches your app’s design system
- Audio-reactive visualizations (waveforms, orbs, pulsing indicators) driven by real-time audio levels
- Custom call flows such as pre-call forms, post-call surveys, or inline chat alongside voice
- Integration into an existing component library (Material UI, Chakra, Radix, etc.)
Installation
The headless hook does not require importing
@thunderphone/widget/style.css since you are providing your own UI. However, you must still install the same @thunderphone/widget package.Basic Usage
Options
Pass these options touseThunderPhone via UseThunderPhoneOptions:
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
publishableKey | string | Yes | — | Publishable API key (pk_live_...). The agent is resolved automatically from the key’s widget configuration. |
theme | 'light' | 'dark' | No | 'light' | Color scheme hint. Does not affect headless rendering, but is available for your custom UI logic. |
primaryColor | string | No | '#6366f1' | Accent color hint. Use this in your custom UI to stay consistent with widget theming. |
title | string | No | 'Voice assistant' | Title hint. Use this in your custom UI to display a label. |
position | 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' | No | 'bottom-right' | Position hint. Use this in your custom UI if you want positional awareness. |
apiBase | string | No | 'https://api.thunderphone.com/v1' | API base URL override. |
onConnect | () => void | No | — | Called when the voice session connects. |
onDisconnect | () => void | No | — | Called when the session ends. |
onError | (error) => void | No | — | Called on errors. Error has error (code) and message fields. |
ringtone | boolean | string | No | false | Play a ringtone while connecting. true for the default ringtone, or a URL string for custom audio. |
Return Value
The hook returns aUseThunderPhoneReturn object:
| Property | Type | Description |
|---|---|---|
state | 'idle' | 'connecting' | 'connected' | 'disconnected' | 'error' | Current connection state. |
connect | () => void | Start a voice session. |
disconnect | () => void | End the current session. |
toggleMute | () => void | Toggle microphone mute on/off. |
isMuted | boolean | Whether the microphone is currently muted. |
error | string | undefined | Error message when state is 'error'. |
agentName | string | undefined | Display name of the connected agent. |
audioLevel | number | Current audio level snapshot (0—1). Updates on each React render cycle. Suitable for non-animation use cases like level indicators or threshold checks. |
audioLevelRef | React.RefObject<number> | A React ref containing the real-time audio level (0—1), updated at ~60 fps outside of React’s render cycle. Use this inside requestAnimationFrame loops for smooth, jank-free animations. |
audio | ReactNode | Invisible element that handles the audio connection — must be rendered. |
Audio-Reactive UI
TheaudioLevelRef ref gives you frame-rate audio levels without triggering React re-renders, making it ideal for driving smooth waveform visualizations, pulsing orbs, or any animation tied to the agent’s voice.
Waveform Example
Pulsing Orb Example
State Machine
Thestate property follows this lifecycle:
| State | Description |
|---|---|
idle | No active session. Ready to call connect(). |
connecting | Session is being established. Disable the call button during this state. |
connected | Voice session is active. The user is talking to the agent. |
disconnected | Session has ended cleanly. Transitions back to idle. |
error | Something went wrong. Check phone.error for the message. Automatically returns to idle after a timeout. |
Examples
With Mute Control
With Ringtone
Play a ringing sound while connecting to simulate a phone call:connecting state and fades out when the agent connects. Pass true for the built-in default ringtone, or a URL string to use your own audio file.
With Event Callbacks
Full Custom UI
Tips
Always render phone.audio
Always render phone.audio
The
phone.audio element is invisible but required. Place it anywhere in your JSX — it renders no visible DOM but manages the WebRTC audio connection internally.Disable the button while connecting
Disable the button while connecting
Handle the error state gracefully
Handle the error state gracefully
When the state is
error, display phone.error to the user. The state will automatically return to idle after a few seconds, so the user can try again.Use callbacks for side effects
Use callbacks for side effects
The
onConnect, onDisconnect, and onError callbacks are ideal for analytics, logging, or triggering other application logic without polling the state.Choose audioLevel vs. audioLevelRef
Choose audioLevel vs. audioLevelRef
Use
audioLevel (the number) for React-rendered UI that changes based on volume — e.g., a threshold-based “speaking” badge. Use audioLevelRef (the ref) inside requestAnimationFrame for smooth 60fps animations like waveforms, since reading a ref does not cause re-renders.