Theming
@threadplane/chat styles everything with CSS custom properties (variables). Every color, radius, font, and spacing value is controlled by a variable prefixed with --tplane-chat-, so matching your app's design system is straightforward.
How Theming Works
Chat components define all design tokens using CSS custom properties on the :host element. The token system supports automatic light/dark switching via prefers-color-scheme and explicit override via a [data-threadplane-chat-theme] attribute.
You don't need to import a stylesheet — each component's encapsulated styles apply the tokens automatically. An optional global stylesheet (@threadplane/chat/chat.css) is there if you want to customize tokens at the :root level.
CSS Custom Properties Reference
Colors
| Token | Light | Dark | Purpose |
|---|---|---|---|
--tplane-chat-bg | rgb(255 255 255) | rgb(17 17 17) | Page/window background |
--tplane-chat-surface | rgb(255 255 255) | rgb(28 28 28) | Cards, elevated surfaces |
--tplane-chat-surface-alt | rgb(251 251 251) | rgb(44 44 44) | Input bg, subtle fills |
--tplane-chat-primary | rgb(28 28 28) | rgb(255 255 255) | User-bubble bg, button fg |
--tplane-chat-on-primary | rgb(255 255 255) | rgb(28 28 28) | User-bubble text, button bg |
--tplane-chat-text | rgb(28 28 28) | rgb(245 245 245) | Assistant text, headings |
--tplane-chat-text-muted | rgb(115 115 115) | rgb(160 160 160) | Labels, placeholders |
--tplane-chat-separator | rgb(229 229 229) | rgb(45 45 45) | Hairlines, borders |
--tplane-chat-muted | rgb(200 200 200) | rgb(60 60 60) | Disabled state |
--tplane-chat-error-bg | #fef2f2 | rgb(45 21 21) | Error background |
--tplane-chat-error-text | #dc2626 | #fca5a5 | Error text |
--tplane-chat-warning-bg | #fffbeb | rgb(45 35 21) | Warning background |
--tplane-chat-warning-text | #b45309 | #fbbf24 | Warning text |
--tplane-chat-success | #16a34a | #4ade80 | Success state |
Shape & Layout
| Token | Light | Dark | Purpose |
|---|---|---|---|
--tplane-chat-radius-bubble | 15px | - | User message bubble radius |
--tplane-chat-radius-input | 20px | - | Input pill radius |
--tplane-chat-radius-card | 8px | - | Card / trace radius |
--tplane-chat-radius-button | 8px | - | Button radius |
--tplane-chat-radius-launcher | 9999px | - | Circular launcher button |
--tplane-chat-max-width | 48rem | - | Message column max width |
Typography
| Token | Light | Dark | Purpose |
|---|---|---|---|
--tplane-chat-font-family | system stack | - | Default font |
--tplane-chat-font-mono | mono stack | - | Code blocks |
--tplane-chat-font-size | 1rem | - | Message text |
--tplane-chat-font-size-sm | 0.875rem | - | UI controls |
--tplane-chat-font-size-xs | 0.75rem | - | Labels, metadata |
--tplane-chat-line-height | 1.6 | - | Assistant text |
--tplane-chat-line-height-tight | 1.5 | - | User bubble |
Light and Dark Mode
Tokens default to light values. Dark mode activates automatically when the user's system preference is prefers-color-scheme: dark, unless the host element has data-threadplane-chat-theme="light" set.
The switching logic is:
- Light variables are applied by default on
:host @media (prefers-color-scheme: dark)overrides with dark variables, unlessdata-threadplane-chat-theme="light"is set[data-threadplane-chat-theme="dark"]forces dark variables regardless of system preference
Customizing the Theme
Override a single token at app root
/* src/styles.css */
:root {
--tplane-chat-primary: oklch(0.55 0.22 264);
}Force a theme via attribute
<!-- Force dark mode regardless of system preference -->
<chat data-threadplane-chat-theme="dark" [agent]="agent" />
<!-- Force light mode regardless of system preference -->
<chat data-threadplane-chat-theme="light" [agent]="agent" />Deep override via the optional global stylesheet
Import @threadplane/chat/chat.css in your global styles to get a :root-level baseline you can override. For me, this is the move when I'm theming several token groups at once — the cost is one more global import to keep track of, but you get everything in a single place:
/* src/styles.css */
@import '@threadplane/chat/chat.css';
:root {
--tplane-chat-primary: oklch(0.55 0.22 264);
--tplane-chat-radius-bubble: 10px;
}Bridge from your design system
If your app already has design-system tokens, keep those tokens as the source of truth and map them into the chat public API. This gives your app a stable --tplane-chat-* control surface without coupling chat internals to your design-system namespace.
:root {
/* App-owned design tokens */
--ds-canvas: #ffffff;
--ds-surface: #f8fafc;
--ds-border: #e2e8f0;
--ds-text-primary: #0f172a;
--ds-text-muted: #64748b;
--ds-accent: #2563eb;
--ds-font-sans: Inter, system-ui, sans-serif;
/* Chat-owned public API */
--tplane-chat-bg: var(--ds-canvas);
--tplane-chat-surface: var(--ds-surface);
--tplane-chat-surface-alt: var(--ds-surface);
--tplane-chat-separator: var(--ds-border);
--tplane-chat-text: var(--ds-text-primary);
--tplane-chat-text-muted: var(--ds-text-muted);
--tplane-chat-primary: var(--ds-accent);
--tplane-chat-font-family: var(--ds-font-sans);
}Use the --tplane-chat-* names at chat boundaries and custom chat-adjacent views. Use your app tokens everywhere else. The bridge is useful because chat token names describe component semantics (primary, surface-alt, separator, radius-bubble) while your design-system tokens can stay product-wide and evolve independently.
Brand Color Example
:root {
/* Brand blues */
--tplane-chat-primary: #2563eb;
--tplane-chat-on-primary: #ffffff;
/* Softer corners */
--tplane-chat-radius-bubble: 12px;
--tplane-chat-radius-input: 16px;
/* Wider content area */
--tplane-chat-max-width: 56rem;
}Migration from --chat-* Tokens
If you previously customized --chat-* tokens, rename them to --tplane-chat-*. Examples:
| Old token | New token |
|---|---|
--chat-bg | --tplane-chat-bg |
--chat-text | --tplane-chat-text |
--chat-user-bg | --tplane-chat-primary |
--chat-user-text | --tplane-chat-on-primary |
--chat-radius-message | --tplane-chat-radius-bubble |
--chat-radius-input | --tplane-chat-radius-input |
--chat-radius-card | --tplane-chat-radius-card |
--chat-max-width | --tplane-chat-max-width |
--chat-error-bg | --tplane-chat-error-bg |
--chat-error-text | --tplane-chat-error-text |
--chat-warning-bg | --tplane-chat-warning-bg |
--chat-warning-text | --tplane-chat-warning-text |
--chat-success | --tplane-chat-success |
The CHAT_THEME_STYLES and CHAT_MARKDOWN_STYLES named exports do not exist in @threadplane/chat. Remove imports of those constants - theme tokens are applied automatically by each component, and custom markdown rendering should use <chat-streaming-md> or your own host styles around renderMarkdown().