---
title: "Theming"
description: "Customize the widget with three required brand colors and choose popup, inline, or page layout."
---

The widget ships with a precompiled, scoped stylesheet. Import it once; Tailwind
is not required in the host application.

```tsx
import '@mordn/chat-widget/styles.css';
```

## Theme contract

`ThemeConfig` is deliberately small: exactly three required hex colors. Omit
`theme` for the stock palette. There is no public `mode` switch or arbitrary CSS
token override API.

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

`theme` is not a prop. It lives in the `client` half of the canonical config
document, which normally arrives from the server when the widget calls
`GET ${apiBase}/bootstrap` — so the usual way to change colors is to publish
them from the dashboard and leave the component as
`<ChatWidget apiBase="/api/chat" />`.

To set them in code instead, pass the same canonical shape as `config`:

```tsx
<ChatWidget
  apiBase="/api/chat"
  config={{
    schemaVersion: 1,
    runtime: { model: 'anthropic/claude-sonnet-4-5' },
    client: {
      theme: {
        backgroundColor: '#171717',
        textColor: '#ededed',
        primaryColor: '#3b82f6',
      },
    },
  }}
/>
```

Explicit values are merged over the published config, so you can override the
theme alone and inherit everything else from the server.

All three values must be valid hex colors. If any value is invalid, the widget
ignores the entire custom theme rather than applying a partial palette. The
remaining color ramp is derived internally from these values.


- **`config.client.theme.backgroundColor`** · `string` · required

  Hex color for the chat background.

- **`config.client.theme.textColor`** · `string` · required

  Hex color for body text. Choose a value with sufficient contrast against the
  background.

- **`config.client.theme.primaryColor`** · `string` · required

  Hex accent used for send controls, links, and focus states.


## Display layout

`display` is a member of `config.client` too, and is published from the
dashboard the same way. To set it in code:

```tsx
<ChatWidget
  apiBase="/api/chat"
  config={{
    schemaVersion: 1,
    runtime: { model: 'anthropic/claude-sonnet-4-5' },
    client: {
      display: {
        layout: 'popup',
        size: 'default',
        defaultOpen: false,
        resizable: true,
        showToggleButton: true,
      },
    },
  }}
/>
```


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

  `popup` is a floating panel, `inline` takes its parent dimensions, and `page`
  provides a full-height conversation surface.

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

  Popup width preset. Ignored by inline and page layouts.

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

  Custom CSS width for popup layout; overrides `size`.

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

  Allow drag resizing in popup layout.

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

  Initial uncontrolled popup state. Persisted panel state can override it after
  mount.

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

  Show the built-in launcher. Controlled mode hides it automatically.

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

  Custom CSS position. The default is bottom-right and includes safe-area insets.


For controlled state, header actions, panel persistence, and the imperative ref,
see [`ChatWidget`](/docs/api/chat-widget) and [AI UX extras](/docs/guides/ai-ux).
