---
title: "React Component"
description: "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

```bash
npm install @thunderphone/widget
```

## Basic Usage

```tsx
import { ThunderPhoneWidget } from '@thunderphone/widget'
import '@thunderphone/widget/style.css'

function App() {
  return (
    <ThunderPhoneWidget
      publishableKey="pk_live_your_publishable_key"
    />
  )
}
```

<Warning>
  You **must** import the CSS file for the widget to render correctly. Without it, the widget will be unstyled.
</Warning>

---

## Props

The component accepts the following props via `ThunderPhoneWidgetProps`:

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `publishableKey` | `string` | Yes | -- | Publishable API key (`pk_live_...`) from **Web Widgets** in the dashboard. 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. |
| `primaryColor` | `string` | No | `'#000000'` (light) / `'#ffffff'` (dark) | CSS color string used as the accent color (call button, waveform, active indicators). |
| `title` | `string` | No | `'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. |
| `apiBase` | `string` | No | `'https://api.thunderphone.com/v1'` | API base URL override. |
| `language` | `string` | No | -- | Per-session language override -- a language code or locale such as `en`, `es`, or `fr-FR`. When unset, the agent's configured language applies. Requires `language` under the key's [browser overrides](/guides/embed-web-widget#browser-overrides). |
| `voice` | `string` | No | -- | Per-session voice override -- a voice name such as `maria`. When unset, the agent's configured voice applies. Requires `voice` under the key's [browser overrides](/guides/embed-web-widget#browser-overrides). |
| `context` | `string` | No | -- | 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. Requires `context` under the key's [browser overrides](/guides/embed-web-widget#browser-overrides). |
| `onConnect` | `() => void` | No | -- | Called when the voice session successfully connects. |
| `onDisconnect` | `() => void` | No | -- | Called when the session ends. |
| `onError` | `(error) => void` | No | -- | Called on errors. The `error` object has `error` (code) and `message` fields. |
| `className` | `string` | No | -- | Additional CSS class name applied to the widget container. |
| `ringtone` | `boolean \| string` | No | `false` | Play a ringtone while connecting. `true` for the default ringtone, or a URL string for custom audio. |

---

## Examples

### Dark Theme with Custom Color

```tsx
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

```tsx
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. Turn each prop you use on under **Browser overrides** for the widget in the dashboard (or in the key's `allowed_overrides`). New widgets accept none, and a session request carrying one that is off fails with `400 override_not_allowed`; see [Browser overrides](/guides/embed-web-widget#browser-overrides).

```tsx
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. The agent treats it as page content, not as instructions. It is truncated server-side to 12,000 characters.

### With Event Callbacks

```tsx
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

```tsx
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"
    />
  )
}
```

```css
.my-custom-widget .tp-button--end {
  background-color: #e74c3c;
}
```

See the [Styling guide](/widget/styling) for all available CSS classes and custom properties.

### With Ringtone

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

```tsx
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:

```tsx
<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

<Tip>
  You only need to set `apiBase` if you are using a self-hosted or proxy API endpoint. The default points to `https://api.thunderphone.com/v1`.
</Tip>

```tsx
<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:

| Field | Type | Description |
|-------|------|-------------|
| `error` | `string` | Machine-readable error code |
| `message` | `string` | Human-readable error description |

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

---

## Next Steps

<CardGroup cols={2}>
  <Card title="Headless Hook" icon="code" href="/widget/headless-hook">
    Need full control over the UI? Use the `useThunderPhone` hook instead.
  </Card>
  <Card title="Styling" icon="palette" href="/widget/styling">
    Customize colors, sizes, and layout with CSS custom properties.
  </Card>
</CardGroup>
