> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runtype.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runtype.com/_mcp/server.

# Embedding the chat widget (React)

Add the Runtype chat widget to a React or Next.js application as a `ChatWidget` component that you generate from your chat Surface.

## Before you begin

Complete the following prerequisites:

* Create a **Product** with a chat **Surface**. For more information, see [Setting up a chat surface](/user-guide/products-surfaces/setting-up-a-chat-surface).
* Create a client token for the Surface, and add your site's origin to its **Allowed origins**. For more information, see [Client tokens and domain restrictions](/user-guide/products-surfaces/client-tokens-and-domain-restrictions).

Use a client token instead of an API key in a chat widget. Client tokens use the `ct_live_` or `ct_test_` prefix and are designed for client-side code. Restrict each token to your site's origins to help protect it from reuse on other sites.

## Install the package

Install the Persona package in your application with the following command:

**`Install Persona`**

```bash title="Install Persona"
npm install @runtypelabs/persona
```

## Get the component code

Copy the generated component from your chat Surface. The steps depend on which dashboard view you use:

#### Workbench

1. Open the chat Surface and select **Embed**.
2. Select **React**.
3. Select a client token in the token list, or select **Create token** to create one.
4. If you selected a live token, paste the token that you saved when you created it into **Saved live token**. The dashboard stores only a hint of a live token, so it can't show the full value again.
5. Click **Copy**.

#### Classic dashboard

1. Open your **Product** and select the chat **Surface**.
2. Select the **Ship** tab.
3. Select **code it myself** in the tool selector.
4. Select **React** under **Integration format**.
5. Select a client token in the second sentence, or select **Create token** to create one.
6. Click **Copy code**.

To copy the code before you create a token, keep **a placeholder** in the token selector. Before you connect the widget, replace `YOUR_CLIENT_TOKEN` in the code with your client token.

To have a coding agent add the widget for you, open **Embed** in the workbench, select your tool under **Build it with a coding agent**, and click **Copy prompt**. Paste the prompt into Claude Code, Cursor, or Codex.

### Generate the component from the CLI

The `runtype persona init` command creates a new Agent and a client token for it, sets the token's allowed origins, and prints a React component that connects to that Agent. Use it to start from a new Agent instead of an existing chat Surface:

**`Generate a React component`**

```bash title="Generate a React component"
runtype persona init --format react --environment test --origins https://example.com --print-snippet
```

Coding agents connected to the Runtype MCP server can generate the React component with the `generate_persona_embed_code` tool.

## Add the component to your app

1. Save the generated code as `ChatWidget.tsx` in your application.
2. If your application doesn't use the Next.js App Router, delete the `'use client'` line at the top of the file. Vite and Create React App don't need it.
3. Render `<ChatWidget />` once, near the root of your application.

The generated component imports `@runtypelabs/persona/widget.css` and initializes the widget with your client token and theme. It mounts the widget to the document body, renders nothing itself, and calls `handle.destroy()` when React unmounts it. Customize the component for your application after you add it.

## Verify the widget

Open your application in a browser. The chat launcher appears on the page. Open it and send a message to confirm that the Surface replies.

In the workbench, **Origins seen** in **Embed** lists your site's origin as **Allowed** after the widget connects.

## Control the widget from your code

`initAgentWidget` returns a handle with the following methods:

* `open()` and `close()`: show or hide the chat panel.
* `submitMessage(text)`: send a message as the visitor.
* `destroy()`: remove the widget.
* `on(event, callback)`: listen for widget events, such as `user:message`, `assistant:message`, `assistant:complete`, `widget:opened`, and `widget:closed`.

The generated component keeps the handle inside `useEffect`. To call it from elsewhere in your component, store it in a ref. The following example opens the widget from a button:

**`ChatWidget.tsx`**

```tsx title="ChatWidget.tsx"
import { useEffect, useRef } from 'react'
import '@runtypelabs/persona/widget.css'
import { initAgentWidget } from '@runtypelabs/persona'
import type { AgentWidgetInitHandle } from '@runtypelabs/persona'

export function ChatWidget() {
  const handleRef = useRef<AgentWidgetInitHandle | null>(null)

  useEffect(() => {
    const handle = initAgentWidget({
      target: 'body',
      config: { apiUrl: 'https://api.runtype.com', clientToken: 'YOUR_CLIENT_TOKEN' },
    })
    handleRef.current = handle
    handle.on('assistant:complete', (message) => console.log(message))
    return () => {
      handle.destroy()
      handleRef.current = null
    }
  }, [])

  return <button onClick={() => handleRef.current?.open()}>Chat with us</button>
}
```

Keep the `config` object from your generated code rather than the shortened one in this example.

## Troubleshooting

**The widget appears but messages fail.** A common cause is that your site's origin isn't in the token's allowed origins. In the workbench, find the origin in **Origins seen** and click **Allow**, or add it in **Access & keys**.

**The code contains `YOUR_CLIENT_TOKEN`.** In the classic dashboard, you copied the code with **a placeholder** selected. Replace `YOUR_CLIENT_TOKEN` with your client token, or copy the code again with a token selected.

**The Copy button is disabled in the workbench.** No client token is selected, or you selected a live token and didn't paste its full value. Select a token, or paste the full live token that you saved when you created it into **Saved live token**. If you no longer have the live token, create a new token.

## Build a custom voice interface

For a voice interface built from your own React components, use `useVoiceClient` from `@runtypelabs/voice/react`. It manages microphone capture, playback, transcripts, and the Agent's **Interruption Mode** setting, which controls whether a caller can interrupt a spoken reply.

Install the voice package with the following command:

**`Install the voice package`**

```bash title="Install the voice package"
npm install @runtypelabs/voice
```

The following component starts and ends a call with an Agent:

**`VoiceCall.tsx`**

```tsx title="VoiceCall.tsx"
import { useVoiceClient } from '@runtypelabs/voice/react'

function VoiceCall({ agentId, clientToken }) {
  const voice = useVoiceClient({ agentId, clientToken })
  return (
    <div>
      <p role="status">{voice.error ?? voice.status}</p>
      <button onClick={() => void voice.startCall()}>Start call</button>
      <button onClick={voice.endCall}>End call</button>
      {voice.canCancel && <button onClick={voice.cancelResponse}>Stop response</button>}
    </div>
  )
}
```

Use a client token that can access the Agent. Start calls from a user click on an HTTPS page or on `localhost`. The hook releases the microphone when the component unmounts. For plain JavaScript usage and the Persona widget adapter, see the [@runtypelabs/voice package](https://www.npmjs.com/package/@runtypelabs/voice).

### Connect ahead on mic hover

A call's first connection and first reply are slower when the browser still has to open the voice connection. To remove part of that delay, spread `getMicButtonPrewarmProps()` onto the button that starts the call:

```tsx
<button {...voice.getMicButtonPrewarmProps()} onClick={() => void voice.startCall()}>
  Start call
</button>
```

When the pointer enters the button, the button receives focus, or the user presses it, the client opens the voice connection and authenticates before the click. `startCall()` then reuses the open connection.

* An early connection doesn't request microphone access, create a conversation, bill, reserve quota, or record usage. The conversation is created when the call starts.
* The client keeps one early connection and closes it after 30 seconds if no call starts. `voice.attached` is `true` while one is ready.
* If the early connection drops, the call still starts normally over a new connection. Connecting early never throws.
* In a browser without `WebSocket`, the client sends one request to `POST /v1/client/agents/{agentId}/voice/prewarm` instead, at most once every 25 seconds. Runtype limits these requests for each client token and Agent.

For a page where voice is the main interaction, pass `keepWarm: true`:

```tsx
const voice = useVoiceClient({ agentId, clientToken, keepWarm: true })
```

With `keepWarm`, the hook connects when it mounts, and Runtype holds the connection for up to 10 minutes. After a drop, the client reconnects on the next mic hover or when the page becomes visible again. It never reconnects while the page is hidden.

Without React, call `voice.attach()` or `voice.prewarm()` on a `VoiceClient`, and call `voice.dispose()` when the view is removed. For a Persona widget, call `bindVoicePrewarmToElement(provider, micButton)` from `@runtypelabs/voice/persona`. This function returns a function that removes the listeners.

## Next steps

* [Widget theming and customization](/user-guide/products-surfaces/widget-theming-and-customization): customize the widget appearance
* [Conversation history in an embedded widget](/developer-guides/guides/client-conversation-history): keep a visitor's conversation history across sessions and devices with an identity proof
* [Client tokens and domain restrictions](/user-guide/products-surfaces/client-tokens-and-domain-restrictions): configure client token origins
* [Quickstart: From Agent to Chat Widget](/user-guide/getting-started/quickstart-from-agent-to-chat-widget): build an Agent and embed it as a chat widget from start to finish
* [Embedding the chat widget (script tag)](/user-guide/products-surfaces/embedding-the-chat-widget-script-tag): embed the widget on non-React sites