---
title: "ChatWidget"
description: "The shipped client contract for ChatWidget: transport, configuration, appearance, layouts, and extension points."
---

`<ChatWidget>` is the client component. It owns chat state, rendering, and calls
the route mounted by [`createChatHandler`](/docs/api/create-chat-handler).

```tsx
'use client';

import { ChatWidget } from '@mordn/chat-widget';
import '@mordn/chat-widget/styles.css';

export function Assistant() {
  return <ChatWidget apiBase="/api/chat" />;
}
```

That is the whole component. On mount the widget calls
`GET ${apiBase}/bootstrap` and the server returns the browser-safe half of the
canonical config — greeting, theme, display, features, starter prompts.

> **Warning**
>
> The widget sends no `userId`, `agentId`, `model`, or system prompt, and there is
> no `X-User-Id` header in the client transport. Identity is resolved on your
> server by `getUserId` (the `getChatUserId` stub the CLI scaffolds), so the
> browser cannot assert who it is or which history it may read. Appearance and
> model policy are server-owned too — see
> [`createChatHandler`](/docs/api/create-chat-handler).

## Transport


- **`apiBase`** · `string` · default `/api/chat`

  Base route for `bootstrap` and the chat, history, upload, memory, and feedback
  subpaths.

- **`headers`** · `Record<string, string>`

  Generic transport headers, for example a host's CSRF token. These are browser
  controlled; never treat them as identity on the server.

- **`requestCredentials`** · `RequestCredentials`

  Fetch credential mode. Use `include` only when a cross-origin handler authenticates
  with cookies, paired with an explicit server `cors` policy.

- **`conversationId`** · `string`

  Open or create a specific conversation id.

- **`initialMessages`** · `UIMessage[]`

  Initial client message state.

- **`className`** · `string`

  Class name applied to the root container.


## Configuration

Appearance and client behaviour are **not** props. They live in the `client`
half of the canonical config document, which normally arrives from the server on
bootstrap. To set it in code instead — for local development, or when you are
not publishing from the dashboard — pass the same canonical shape as `config`.


- **`config`** · `AgentConfig`

  Full canonical config for caller overrides or an authenticated preview. Explicit
  values are merged over the published config returned by `bootstrap`.


```tsx
<ChatWidget
  apiBase="/api/chat"
  config={{
    schemaVersion: 1,
    runtime: { model: 'anthropic/claude-sonnet-4-5' },
    client: {
      greeting: 'How can I help?',
      theme: {
        backgroundColor: '#171717',
        textColor: '#ededed',
        primaryColor: '#3b82f6',
      },
      display: { layout: 'popup', size: 'default' },
      features: { fileUpload: true },
      starterPrompts: [{ title: 'What can you help me with?' }],
    },
  }}
/>
```

The fields below are all members of `config.client`.


- **`config.client.greeting`** · `string`

  Empty-state greeting headline.

- **`config.client.subGreeting`** · `string`

  Faint line under the greeting headline.

- **`config.client.assistantName`** · `string`

  The assistant's name, used as its identity in the UI.

- **`config.client.capabilitiesPrompt`** · `string`

  Fallback prompt affordance shown in the empty state.

- **`config.client.starterPrompts`** · `{ title: string; subtitle?: string }[]`

  Static empty-state prompts.

- **`config.client.feedback`** · `boolean`

  Show answer ratings.

- **`config.client.streamingThrottleMs`** · `number`

  Throttle interval for streaming re-renders.

- **`config.client.persistState`** · `boolean` · default `true`

  Persist the uncontrolled popup's open/closed state, scoped to the opaque
  storage scope issued by `bootstrap`.

- **`config.client.allowAutoReopen`** · `boolean` · default `false`

  Allow host-driven programmatic reopen after dismissal. Closing is always
  allowed.


### Theme

`ThemeConfig` contains exactly three required hex colors. Omit `theme` for the
stock palette. Partial themes, `mode`, and arbitrary `tokens` are not public
APIs.

```ts
interface ThemeConfig {
  backgroundColor: string;
  textColor: string;
  primaryColor: string;
}
```

See [Theming](/docs/guides/theming).

### Features


- **`config.client.features.fileUpload`** · `boolean`

  Show upload controls. The server also needs a `StorageAdapter`.

- **`config.client.features.fileUploadAccept`** · `string` · default `image/*`

  Browser file-picker filter; mirror the server allow-list.

- **`config.client.features.fileUploadMaxBytes`** · `number`

  Client UX cap only. `UploadPolicy.maxBytes` is authoritative.

- **`config.client.features.webSearch`** · `boolean`

  Expose the web-search feature state in the UI.

- **`config.client.features.voiceInput`** · `boolean` · default `true`

  Microphone button for voice dictation: speech becomes text in the textbox, the user edits and sends. Uses the browser's built-in speech recognition (Chrome, Edge, Safari) — no server, no key, no audio through your host or mordn. Not rendered where the browser lacks the API. Requires `@mordn/chat-widget` ≥ 0.20.0.

- **`config.client.features.voiceInputLanguage`** · `string`

  BCP-47 language for dictation (e.g. `en-GB`, `de`). Defaults to the page's `<html lang>`, then the browser language.


### Display


- **`config.client.display.layout`** · `'popup' \| 'inline' \| 'page'` · default `popup`

  Floating panel, embedded surface, or full-height chat page.

- **`config.client.display.size`** · `'compact' \| 'default' \| 'large' \| 'full'` · default `default`

  Popup width preset.

- **`config.client.display.width`** · `string`

  Custom popup width.

- **`config.client.display.resizable`** · `boolean` · default `true`

  Popup resizing.

- **`config.client.display.defaultOpen`** · `boolean` · default `false`

  Initial uncontrolled popup state.

- **`config.client.display.starterPromptsLayout`** · `'list' \| 'grid'` · default `list`

  Empty-state prompt layout.

- **`config.client.display.showToggleButton`** · `boolean` · default `true`

  Built-in popup launcher.

- **`config.client.display.toggleButtonPosition`** · `{ bottom?: string; right?: string }`

  Custom launcher position. Defaults include safe-area insets.

- **`config.client.display.keyboardShortcut`** · `string \| false`

  Open the widget with a keyboard combo such as `"mod+i"`. Off by default; the
  widget never claims a host keybinding implicitly.


## Panel state


- **`open`** · `boolean`

  Controlled popup state.

- **`onOpenChange`** · `(open: boolean) => void`

  Controlled-state callback.

- **`onStateChange`** · `(open: boolean) => void`

  Observe panel state changes.

- **`onClose`** · `() => void`

  Called when the close control is used.

- **`headerActions`** · `ReactNode`

  Host-owned actions in the header.


Panel persistence and programmatic reopen are configured with
`config.client.persistState` and `config.client.allowAutoReopen` above.

## Guidance and extension points


- **`getStarterPrompts`** · `() => StarterPrompt[] \| Promise<StarterPrompt[]>`

  Runtime prompts; a non-empty result wins over the static
  `config.client.starterPrompts` list.

- **`context`** · `ChatContext \| 'auto' \| (() => ChatContext \| Promise<ChatContext>)`

  Per-turn client context: a static object, `'auto'` for the built-in safe page
  capture, or a function resolved fresh at send time. Ignored by default unless
  the server validates it with `getContext` or opts in with `trustClientContext`.

- **`inputPlugins`** · `InputPlugin[]`

  Trigger-based input autocomplete.

- **`toolRenderers`** · `Record<string, ToolRenderer>`

  Full custom tool rendering.

- **`actionRenderers`** · `Record<string, ActionRenderer>`

  Declarative action-result cards.

- **`onFeedback`** · `(feedback: FeedbackEvent) => void`

  Observe client submissions. Ratings are shown only when
  `config.client.feedback` is on.


## Imperative handle

```ts
interface ChatWidgetHandle {
  open(): void;
  close(): void;
  toggle(): void;
  readonly isOpen: boolean;
}
```

Opening obeys `config.client.allowAutoReopen`; closing is always allowed. See
[AI UX extras](/docs/guides/ai-ux) for examples.
