ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Widget

React Component

Embed the ThunderPhone voice widget in a React application

The ThunderPhoneWidget component renders a glassmorphic call bar with built-in controls for muting, ending the call, and displaying connection status. It is the fastest way to add voice AI to a React app.

Installation

npm install @thunderphone/widget

Basic Usage

import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'
 
function App() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
    />
  )
}

Props

The component accepts the following props via ThunderPhoneWidgetProps:

PropTypeRequiredDefaultDescription
publishableKeystringYes--Publishable API key (pk_live_...) from the Developers settings. The agent is resolved automatically from the key's widget configuration.
theme'light' | 'dark'No'light'Color scheme. Applies tp--light or tp--dark class to the widget root.
primaryColorstringNo'#000000' (light) / '#ffffff' (dark)CSS color string used as the accent color (call button, waveform, active indicators).
titlestringNo'Voice assistant'Text displayed in the widget bar.
position'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'No'bottom-right'Fixed viewport position for the widget.
apiBasestringNo'https://api.thunderphone.com/v1'API base URL override.
languagestringNo--Per-session language override -- a language code or locale such as en, es, or fr-FR. When unset, the agent's configured language applies.
voicestringNo--Per-session voice override -- a voice name such as maria. When unset, the agent's configured voice applies.
contextstringNo--Per-session factual page or site context passed to the agent (for example, details of the page the visitor is viewing). Truncated server-side to 12,000 characters.
onConnect() => voidNo--Called when the voice session successfully connects.
onDisconnect() => voidNo--Called when the session ends.
onError(error) => voidNo--Called on errors. The error object has error (code) and message fields.
classNamestringNo--Additional CSS class name applied to the widget container.
ringtoneboolean | stringNofalsePlay a ringtone while connecting. true for the default ringtone, or a URL string for custom audio.

Examples

Dark Theme with Custom Color

import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'
 
function App() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      theme="dark"
      primaryColor="#8b5cf6"
      title="Talk to our AI"
    />
  )
}

Custom Position

import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'
 
function App() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      position="bottom-left"
    />
  )
}

Per-Session Language, Voice, and Context

The language, voice, and context props are forwarded to the session request (POST /widget/session) when a call starts, overriding the agent's configured defaults for that session:

import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'
 
function PricingPageWidget() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      language="es"
      voice="maria"
      context="Page: Pricing. Plans: Starter $29/mo, Pro $99/mo. Annual billing saves 20%."
    />
  )
}

Use context to give the agent factual knowledge about the page the visitor is on -- product details, pricing, or page-specific FAQs. It is truncated server-side to 12,000 characters.

With Event Callbacks

import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'
 
function SupportWidget() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      onConnect={() => {
        console.log('Voice session connected')
        analytics.track('widget_call_started')
      }}
      onDisconnect={() => {
        console.log('Voice session ended')
        analytics.track('widget_call_ended')
      }}
      onError={(error) => {
        console.error(`Widget error: ${error.error} - ${error.message}`)
      }}
    />
  )
}

With Custom Styling

import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'
 
function BrandedWidget() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      primaryColor="#4a90d9"
      className="my-custom-widget"
    />
  )
}
.my-custom-widget .tp-button--end {
  background-color: #e74c3c;
}

See the Styling guide for all available CSS classes and custom properties.

With Ringtone

Play a phone-ringing sound while the connection is being established:

import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'
 
function PhoneWidget() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
      ringtone={true}
    />
  )
}

Use a custom ringtone by passing an audio file URL:

<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  ringtone="https://example.com/my-ringtone.mp3"
/>

The ringtone loops while the widget is in the connecting state and fades out smoothly when the agent connects.

With Custom API Base

<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  apiBase="https://your-proxy.example.com/v1"
/>

Error Handling

When the onError callback fires, it receives an error object with two fields:

FieldTypeDescription
errorstringMachine-readable error code
messagestringHuman-readable error description

Common error codes include domain not allowed, agent not found, and invalid API key.


Next Steps