The ChatWidget component

<ChatWidget> is the client component. It owns chat state, rendering, and calls the route mounted by createChatHandler.

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

Transport

apiBasestringdefault: /api/chat

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

headersRecord<string, string>

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

requestCredentialsRequestCredentials

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

conversationIdstring

Open or create a specific conversation id.

initialMessagesUIMessage[]

Initial client message state.

classNamestring

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.

configAgentConfig

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

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

Empty-state greeting headline.

config.client.subGreetingstring

Faint line under the greeting headline.

config.client.assistantNamestring

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

config.client.capabilitiesPromptstring

Fallback prompt affordance shown in the empty state.

config.client.starterPrompts{ title: string; subtitle?: string }[]

Static empty-state prompts.

config.client.feedbackboolean

Show answer ratings.

config.client.streamingThrottleMsnumber

Throttle interval for streaming re-renders.

config.client.persistStatebooleandefault: true

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

config.client.allowAutoReopenbooleandefault: 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.

code
interface ThemeConfig {
  backgroundColor: string;
  textColor: string;
  primaryColor: string;
}

See Theming.

Features

config.client.features.fileUploadboolean

Show upload controls. The server also needs a StorageAdapter.

config.client.features.fileUploadAcceptstringdefault: image/*

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

config.client.features.fileUploadMaxBytesnumber

Client UX cap only. UploadPolicy.maxBytes is authoritative.

config.client.features.webSearchboolean

Expose the web-search feature state in the UI.

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.widthstring
Custom popup width.
config.client.display.resizablebooleandefault: true
Popup resizing.
config.client.display.defaultOpenbooleandefault: false
Initial uncontrolled popup state.
config.client.display.starterPromptsLayout'list' | 'grid'default: list
Empty-state prompt layout.
config.client.display.showToggleButtonbooleandefault: true
Built-in popup launcher.
config.client.display.toggleButtonPosition{ bottom?: string; right?: string }

Custom launcher position. Defaults include safe-area insets.

config.client.display.keyboardShortcutstring | 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

openboolean
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.
headerActionsReactNode
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.

contextChatContext | '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.

inputPluginsInputPlugin[]
Trigger-based input autocomplete.
toolRenderersRecord<string, ToolRenderer>
Full custom tool rendering.
actionRenderersRecord<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

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

Opening obeys config.client.allowAutoReopen; closing is always allowed. See AI UX extras for examples.