Skip to main content
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.
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 instead.

Themes

The theme prop controls the widget’s color scheme. It applies a tp--light or tp--dark class to the widget root:
<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  theme="dark"
/>
ThemeClassDescription
'light'tp--lightLight background with dark text. Default.
'dark'tp--darkDark 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. These are set on the .tp-widget root element:
PropertyDefault (light)Default (dark)Description
--tp-accent#6366f1#6366f1Primary accent color (call button, active indicators). Set via the primaryColor prop.
--tp-bgrgba(255, 255, 255, 0.8)rgba(23, 23, 23, 0.8)Widget background color (with transparency for glassmorphism).
--tp-text#1a1a1a#f5f5f5Primary text color.
--tp-text-secondary#6b7280#a1a1aaSecondary text color (status text, timer).
--tp-borderrgba(0, 0, 0, 0.1)rgba(255, 255, 255, 0.1)Border color.

Overriding Custom Properties

You can set the accent color via the primaryColor prop:
<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  primaryColor="#e11d48"
/>
Or override any custom property with CSS:
.tp-widget {
  --tp-accent: #e11d48;
  --tp-bg: rgba(0, 0, 0, 0.9);
  --tp-text: #ffffff;
  --tp-text-secondary: #a1a1aa;
  --tp-border: rgba(255, 255, 255, 0.15);
}

CSS Classes

All widget classes are prefixed with tp- to avoid conflicts with your existing styles.
ClassElementDescription
.tp-widgetOuter containerThe root wrapper. Applies the glassmorphic bar style with backdrop blur.
.tp--lightTheme modifierApplied to .tp-widget when theme="light".
.tp--darkTheme modifierApplied to .tp-widget when theme="dark".
.tp-buttonAll buttonsBase style for all circular buttons (44px default).
.tp-button--startStart / mic buttonThe primary call button. Uses --tp-accent as background.
.tp-button--muteMute toggle buttonShown during an active call to mute/unmute the mic.
.tp-button--endEnd call buttonShown during an active call to hang up.
.tp-statusStatus text containerWraps the title and connection state text.
.tp-status__nameTitle / agent nameDisplays the title prop value or the connected agent’s name.
.tp-status__textState / timer textShows connection state (e.g., “Connecting…”) or call duration.

Examples

Custom Accent via Props

The simplest way to brand the widget:
<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:
/* Emerald theme */
.tp-widget {
  --tp-accent: #059669;
  --tp-bg: rgba(236, 253, 245, 0.85);
  --tp-text: #064e3b;
  --tp-text-secondary: #047857;
  --tp-border: rgba(5, 150, 105, 0.2);
}

Custom Size

Make the widget larger or smaller by adjusting the button dimensions:
/* Larger buttons */
.tp-button {
  width: 56px;
  height: 56px;
}

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

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

Hide Status Text

If you only want the buttons without the status labels:
.tp-status {
  display: none;
}

Theme-Specific Overrides

Target a specific theme with the theme class:
/* Only affect dark theme */
.tp--dark .tp-button--start {
  box-shadow: 0 0 20px rgba(99, 102, 241, 0.4);
}

/* Only affect light theme */
.tp--light .tp-widget {
  --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:
<ThunderPhoneWidget
  publishableKey="pk_live_your_publishable_key"
  theme="dark"
  className="support-widget"
/>
Then target that class in your CSS:
.support-widget.tp-widget {
  --tp-accent: #8e44ad;
}

.support-widget .tp-status__name {
  font-weight: 700;
}
This lets you have multiple widget instances on the same page with different styles.

Fully Custom UI

If CSS overrides are not enough, the 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.
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>
  )
}
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.