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:
The hook is headless: it does not accept the
ThunderPhoneWidget appearance props (theme, primaryColor, title, position, className). Passing them is a TypeScript error — presentation is entirely yours to build.Return Value
The hook returns aUseThunderPhoneReturn object:
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 conversation. The level reflects whichever is louder: the agent’s voice or the visitor’s microphone.
Waveform Example
Pulsing Orb Example
Speaking Indicator Example
For React-rendered UI that changes with volume — like a threshold-based “speaking” badge — sampleaudioLevelRef.current on an interval and store the result in state:
State Machine
Thestate property follows this lifecycle:
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.Handle the error state gracefully
Handle the error state gracefully
When the state is
error, display phone.error to the user and keep your call button enabled. The hook does not leave the error state on its own — calling connect() again starts a fresh attempt and clears the previous error.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.Read audio levels from audioLevelRef
Read audio levels from audioLevelRef
audioLevelRef is the only live audio-level source. Read audioLevelRef.current inside requestAnimationFrame for smooth animations like waveforms (reading a ref does not cause re-renders), or sample it on an interval and store the result in state for React-rendered UI. The audioLevel number is deprecated and always 0 — do not build logic on it.