> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Styling

> Customize the appearance of the ThunderPhone voice widget with CSS

The widget renders as a glassmorphic bar with built-in light and dark themes. Customization is available at three levels: props for common options, CSS custom properties for theming, and CSS class overrides for full control.

<Note>
  These styling options apply to the pre-built widget rendered by the `ThunderPhoneWidget` React component and the `ThunderPhone.mount()` CDN method. If you need a completely custom UI, use the [headless hook](/widget/headless-hook) instead.
</Note>

***

## Themes

The `theme` prop controls the widget's color scheme. It applies a `tp--light` or `tp--dark` class to the widget root:

```tsx theme={null}
<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  theme="dark"
/>
```

| Theme     | Class       | Description                               |
| --------- | ----------- | ----------------------------------------- |
| `'light'` | `tp--light` | Light background with dark text. Default. |
| `'dark'`  | `tp--dark`  | Dark background with light text.          |

Both themes use the glassmorphic bar design with backdrop blur and subtle transparency.

***

## CSS Custom Properties

The widget exposes CSS custom properties (variables) that you can override to change colors without touching individual classes. They are defined by the theme class (`.tp--light` or `.tp--dark`) applied to the `.tp-widget` root:

| Property             | Default (light)                         | Default (dark)                           | Description                                                                                                                    |
| -------------------- | --------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `--tp-accent`        | `#000`                                  | `#fff`                                   | Accent color: start button, waveform bars, connecting dot, connected-status text. Set **inline** from the `primaryColor` prop. |
| `--tp-bg`            | `rgba(255, 255, 255, 0.82)`             | `rgba(15, 15, 15, 0.85)`                 | Bar background (translucent; blurred by `--tp-glass`).                                                                         |
| `--tp-surface`       | `rgba(0, 0, 0, 0.04)`                   | `rgba(255, 255, 255, 0.07)`              | Mute-button background.                                                                                                        |
| `--tp-surface-hover` | `rgba(0, 0, 0, 0.07)`                   | `rgba(255, 255, 255, 0.12)`              | Mute-button hover background.                                                                                                  |
| `--tp-border`        | `rgba(0, 0, 0, 0.08)`                   | `rgba(255, 255, 255, 0.1)`               | Bar and button borders.                                                                                                        |
| `--tp-border-hover`  | `rgba(0, 0, 0, 0.14)`                   | `rgba(255, 255, 255, 0.18)`              | Border color on hover.                                                                                                         |
| `--tp-text`          | `rgba(0, 0, 0, 0.88)`                   | `rgba(255, 255, 255, 0.95)`              | Primary text (title, agent name).                                                                                              |
| `--tp-text-2`        | `rgba(0, 0, 0, 0.5)`                    | `rgba(255, 255, 255, 0.55)`              | Secondary text (subtitle, status line, call timer).                                                                            |
| `--tp-glass`         | `blur(32px) saturate(180%)`             | `blur(32px) saturate(180%)`              | `backdrop-filter` that creates the glass effect on the bar.                                                                    |
| `--tp-shadow`        | three-layer shadow stack                | three-layer shadow stack                 | The bar's `box-shadow` (ring + near + far layers).                                                                             |
| `--tp-shadow-hover`  | three-layer shadow stack                | three-layer shadow stack                 | Declared for hover elevation; not currently applied by any rule.                                                               |
| `--tp-glow`          | `inset 0 1px 0 0 rgba(255,255,255,0.5)` | `inset 0 1px 0 0 rgba(255,255,255,0.06)` | Inner top highlight layered onto the bar shadow.                                                                               |
| `--tp-connected`     | `#059669`                               | `#34d399`                                | Connected-state indicator color (status dot).                                                                                  |
| `--tp-error`         | `#dc2626`                               | `#fb7185`                                | Error-state status text color.                                                                                                 |
| `--tp-end-bg`        | `rgba(239, 68, 68, 0.08)`               | `rgba(251, 113, 133, 0.12)`              | End-call button background.                                                                                                    |
| `--tp-end-color`     | `#ef4444`                               | `#fb7185`                                | End-call button icon color.                                                                                                    |
| `--tp-end-border`    | `rgba(239, 68, 68, 0.12)`               | `rgba(251, 113, 133, 0.15)`              | End-call button border.                                                                                                        |
| `--tp-end-hover`     | `rgba(239, 68, 68, 0.14)`               | `rgba(251, 113, 133, 0.2)`               | End-call button hover background.                                                                                              |
| `--tp-idle-opacity`  | `0.4`                                   | `0.3`                                    | Declared for idle-state dimming; not currently applied by any rule.                                                            |

### Overriding Custom Properties

Set the accent color via the `primaryColor` prop:

```tsx theme={null}
<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  primaryColor="#e11d48"
/>
```

<Warning>
  `--tp-accent` is set as an **inline style** from the `primaryColor` prop, so stylesheet overrides of `--tp-accent` have no effect. Change the accent with the prop. Every other custom property can be overridden in CSS.
</Warning>

Override the other custom properties with CSS. Use a two-class selector (`.tp-widget.tp--light` / `.tp-widget.tp--dark`) so your rule outweighs the theme class that defines the defaults, regardless of stylesheet order:

```css theme={null}
.tp-widget.tp--light {
  --tp-bg: rgba(0, 0, 0, 0.9);
  --tp-text: rgba(255, 255, 255, 0.95);
  --tp-text-2: rgba(255, 255, 255, 0.55);
  --tp-border: rgba(255, 255, 255, 0.15);
}
```

***

## CSS Classes

All widget classes are prefixed with `tp-` to avoid conflicts with your existing styles.

| Class                         | Element                | Description                                                                                                                                                                    |
| ----------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `.tp-widget`                  | Root wrapper           | Fixed-position container (`position: fixed`, corner set by the `position` prop, `z-index: 9999`). Carries the theme class and base font settings; no visual chrome of its own. |
| `.tp--light` / `.tp--dark`    | Theme modifiers        | Applied to `.tp-widget` alongside the theme; define all `--tp-*` custom properties.                                                                                            |
| `.tp-bar`                     | The bar                | The glassmorphic pill itself: background, backdrop blur, border, `99px` radius, shadow. `300px` wide.                                                                          |
| `.tp-meta`                    | Text block             | Container for all text -- title and subtitle when idle, agent name and status during a call.                                                                                   |
| `.tp-name`                    | Primary label          | Shows the `title` prop when idle, and the connected agent's name (falling back to `title`) during a call.                                                                      |
| `.tp-sub`                     | Subtitle               | The "Available now" line shown when idle.                                                                                                                                      |
| `.tp-start`                   | Idle call button       | The circular accent start button (42px). Uses `--tp-accent` as background.                                                                                                     |
| `.tp-dot`                     | Connecting dot         | Pulsing accent dot shown at the left of the bar while connecting.                                                                                                              |
| `.tp-wave` / `.tp-wave--idle` | Waveform               | The five-bar waveform. `--idle` adds the slow breathing animation; during a call the bars are audio-reactive.                                                                  |
| `.tp-button`                  | In-call buttons        | Base style for the in-call controls (42px, rounded 12px).                                                                                                                      |
| `.tp-button-group`            | Button row             | Wraps the mute and end buttons during a call.                                                                                                                                  |
| `.tp-button--start`           | Connect button variant | Accent-colored variant shown while a call is starting.                                                                                                                         |
| `.tp-button--mute`            | Mute toggle            | Mutes/unmutes the mic during a call. Uses `--tp-surface`.                                                                                                                      |
| `.tp-button--end`             | End call button        | Hangs up. Uses the `--tp-end-*` palette.                                                                                                                                       |
| `.tp-button--loading`         | Loading modifier       | Dims the button while connecting.                                                                                                                                              |
| `.tp-icon` / `.tp-spin`       | Icons                  | Button icon sizing; `tp-spin` animates the connecting spinner.                                                                                                                 |
| `.tp-status`                  | In-call status block   | Wraps the status line during connecting/connected/error states.                                                                                                                |
| `.tp-status__text`            | Status line            | Connection state text (e.g., "Connecting...") or the call timer. Gets `.tp-status--connected` (accent color) or `.tp-status--error` (error color) per state.                   |
| `.tp-status__name`            | Agent-name slot        | Part of the status block, but not rendered in the current bar layout -- the agent name appears in `.tp-name` instead.                                                          |
| `.tp-status__dot`             | Status dot             | Pulsing connected-state dot style (uses `--tp-connected`).                                                                                                                     |

***

## Examples

### Custom Accent via Props

The simplest way to brand the widget:

```tsx theme={null}
<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  theme="light"
  primaryColor="#059669"
  title="Talk to support"
/>
```

### Custom Colors via CSS

Override the custom properties for full color control. Remember that the accent comes from the `primaryColor` prop, not CSS:

```tsx theme={null}
<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  primaryColor="#059669"
/>
```

```css theme={null}
/* Emerald theme for everything else */
.tp-widget.tp--light {
  --tp-bg: rgba(236, 253, 245, 0.85);
  --tp-text: rgba(6, 78, 59, 0.95);
  --tp-text-2: rgba(4, 120, 87, 0.8);
  --tp-border: rgba(5, 150, 105, 0.2);
}
```

### Custom Size

Make the widget larger or smaller by adjusting the bar, button, and text dimensions:

```css theme={null}
/* Wider bar */
.tp-bar {
  width: 340px;
}

/* Larger buttons (42px by default) */
.tp-start,
.tp-button {
  width: 56px;
  height: 56px;
}

/* Larger text */
.tp-name {
  font-size: 16px;
}

.tp-sub,
.tp-status__text {
  font-size: 14px;
}
```

### Hide the Text Labels

All of the widget's text lives in `.tp-meta`. Hide it entirely to keep just the waveform and buttons:

```css theme={null}
.tp-meta {
  display: none;
}
```

Or hide individual pieces:

```css theme={null}
/* Hide only the idle "Available now" subtitle */
.tp-sub {
  display: none;
}

/* Hide only the in-call status line (connection state / timer) */
.tp-status {
  display: none;
}
```

<Note>
  The idle label lives in `.tp-name`/`.tp-sub`, not `.tp-status` -- hiding `.tp-status` alone still shows the title when the widget is idle.
</Note>

### Theme-Specific Overrides

Target a specific theme with the theme class:

```css theme={null}
/* Only affect dark theme */
.tp--dark .tp-start {
  box-shadow: 0 0 20px rgba(255, 255, 255, 0.25);
}

/* Only affect light theme */
.tp-widget.tp--light {
  --tp-bg: rgba(255, 255, 255, 0.95);
}
```

***

## Scoping with className

When using the React component, pass a `className` prop to scope your overrides to a specific widget instance:

```tsx theme={null}
<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  theme="dark"
  className="support-widget"
/>
```

Then target that class in your CSS:

```css theme={null}
.support-widget.tp--dark {
  --tp-bg: rgba(30, 30, 46, 0.9);
}

.support-widget .tp-name {
  font-weight: 700;
}
```

This lets you have multiple widget instances on the same page with different styles. Give each instance its own accent through its `primaryColor` prop (CSS cannot override `--tp-accent` -- it is set inline).

***

## Fully Custom UI

If CSS overrides are not enough, the [headless hook](/widget/headless-hook) gives you full control. You provide all the HTML and styling while `useThunderPhone` handles the voice session. The hook also provides `audioLevelRef` for building audio-reactive visualizations like waveforms.

```tsx theme={null}
import { useThunderPhone } from '@thunderphone/widget'

function MyWidget() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })

  return (
    <div className="my-totally-custom-widget">
      {/* Your own buttons, animations, layouts -- anything */}
      <button onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}>
        {phone.state === 'connected' ? 'Hang up' : 'Call us'}
      </button>
      {phone.audio}
    </div>
  )
}
```

<Tip>
  The headless hook is the right choice when you need audio-reactive animations, custom layouts, or integration into an existing component library. CSS overrides and custom properties are better for quick theming adjustments.
</Tip>
