Theming

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

code
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.

code
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:

code
<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.backgroundColorstringrequired

Hex color for the chat background.

config.client.theme.textColorstringrequired

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

config.client.theme.primaryColorstringrequired

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:

code
<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.widthstring

Custom CSS width for popup layout; overrides size.

config.client.display.resizablebooleandefault: true

Allow drag resizing in popup layout.

config.client.display.defaultOpenbooleandefault: false

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

config.client.display.showToggleButtonbooleandefault: 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 and AI UX extras.