Skip to main content
The 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-built ThunderPhoneWidget 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

You must render phone.audio somewhere in your component tree. It is an invisible React element that manages the underlying audio connection. If you omit it, no audio will play and the session will not work.

Options

Pass these options to useThunderPhone 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 a UseThunderPhoneReturn object:

Audio-Reactive UI

The audioLevelRef 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 — sample audioLevelRef.current on an interval and store the result in state:
Always read levels from audioLevelRef.current. The audioLevel number on the return object is deprecated and always 0 — any logic built on it will silently read zero.

State Machine

The state property follows this lifecycle:

Examples

With Mute Control

With Ringtone

Play a ringing sound while connecting to simulate a phone call:
The ringtone loops during the 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

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.
The connecting state can last 1-3 seconds. Disable the call button during this state to prevent duplicate connection attempts.
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.
The onConnect, onDisconnect, and onError callbacks are ideal for analytics, logging, or triggering other application logic without polling the state.
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.