Theming
Customize the appearance of your chat UI with themes and colors.
New in 0.4.0
Four status colours (success, danger, warning, info, each with a ...Bg tint) colour the error banner and the outcomes of approval and question cards, the usage-limit notice and the history badges. themeColors accepts every colour key.
Theme Modes
Chat UI supports dark and light themes:
// Dark theme (default)
<Chat
baseUrl="/api/agents"
agentId="assistant"
theme="dark"
/>
// Light theme
<Chat
baseUrl="/api/agents"
agentId="assistant"
theme="light"
/>The theme is read when the chat mounts
theme and themeColors set the initial look. Changing them on a mounted Chat does not re-theme it. To switch at runtime, remount the chat with a key (for example key={theme}); see Dynamic Theme Switching.
Custom Colors
Override specific colors using themeColors:
<Chat
baseUrl="/api/agents"
agentId="assistant"
theme="dark"
themeColors={{
bgPrimary: "#1a1a2e",
bgSecondary: "#16213e",
bgTertiary: "#0f3460",
textPrimary: "#eaeaea",
textSecondary: "#a0a0a0",
accentPrimary: "#e94560",
}}
/>Available Color Properties
| Property | Description | Default (Dark) | Default (Light) |
|---|---|---|---|
bgPrimary | Main background | #212121 | #ffffff |
bgSecondary | Secondary background (sidebar, assistant messages) | #171717 | #f7f7f8 |
bgTertiary | Tertiary background (inputs, chips) | #2f2f2f | #ececec |
bgHover | Hover state background | #3f3f3f | #e3e3e3 |
textPrimary | Primary text | #ececec | #171717 |
textSecondary | Secondary text | #b4b4b4 | #6b6b6b |
textTertiary | Tertiary text | #8e8e8e | #8e8e8e |
textInverse | Text on accent backgrounds | #171717 | #ffffff |
borderPrimary | Primary border | #3f3f3f | #e3e3e3 |
borderSecondary | Secondary border | #2f2f2f | #ececec |
accentPrimary | Primary accent (buttons, focus) | #10a37f | #10a37f |
accentSecondary | Secondary accent | #1a7f64 | #1a7f64 |
success, successBg | Approved / answered outcomes: icon colour and tinted background | #34d399, rgba(16, 185, 129, 0.12) | #047857, rgba(16, 185, 129, 0.12) |
danger, dangerBg | Error banner, denied outcomes, invalid input, "Limit reached" badge | #f87171, rgba(239, 68, 68, 0.12) | #b91c1c, rgba(239, 68, 68, 0.1) |
warning, warningBg | Expired outcomes, a deadline close to running out, the usage-limit notice, "Waiting for you" badge, image warning | #fbbf24, rgba(245, 158, 11, 0.14) | #92400e, rgba(245, 158, 11, 0.16) |
info, infoBg | Informational notices and announcements, "Working" badge | #60a5fa, rgba(59, 130, 246, 0.12) | #1d4ed8, rgba(59, 130, 246, 0.1) |
themeColors takes every one of these keys (it is ChatTheme["colors"]). The light theme ships darker status colours so they stay readable on white.
ChatTheme also declares borderRadius, spacing and fonts. They are part of the type, but ChatThemeProvider only applies colors: shape and fonts are styled with your own CSS (see Component-Level Styling).
Theme presets
Seventeen ready-made palettes are exported. Pass one preset's colours to themeColors and its mode to theme:
import { Chat, getThemePreset, themePresets, darkPresets, lightPresets } from "@cognipeer/chat-ui";
const preset = getThemePreset("ocean")!;
<Chat
baseUrl="/api/agents"
agentId="assistant"
theme={preset.theme.mode}
themeColors={preset.theme.colors}
/>| Mode | Preset ids |
|---|---|
| Dark | midnight, ocean, purple-haze, sunset, emerald, onyx, nord |
| Light | chatgpt-light, snow, arctic, cloud, pearl, sky, sandstone, rose-garden, mint, lavender |
Each ThemePreset is { id, name, description, preview: { bg, accent, text }, theme }. Presets set the twelve base colours; the status colours keep the defaults above unless you add them.
CSS Variables
The library uses CSS variables for theming. Override them in your CSS:
:root {
--chat-bg-primary: #212121;
--chat-bg-secondary: #171717;
--chat-bg-tertiary: #2f2f2f;
--chat-bg-hover: #3f3f3f;
--chat-text-primary: #ececec;
--chat-text-secondary: #b4b4b4;
--chat-text-tertiary: #8e8e8e;
--chat-text-inverse: #171717;
--chat-border-primary: #3f3f3f;
--chat-border-secondary: #2f2f2f;
--chat-accent-primary: #10a37f;
--chat-accent-secondary: #1a7f64;
/* Status colours: the colour and a tinted background for each */
--chat-success: #34d399;
--chat-success-bg: rgba(16, 185, 129, 0.12);
--chat-danger: #f87171;
--chat-danger-bg: rgba(239, 68, 68, 0.12);
--chat-warning: #fbbf24;
--chat-warning-bg: rgba(245, 158, 11, 0.14);
--chat-info: #60a5fa;
--chat-info-bg: rgba(59, 130, 246, 0.12);
}Light Theme Variables
The light theme is the .chat-theme-light class, which the chat puts on its root when theme="light":
.chat-theme-light {
--chat-bg-primary: #ffffff;
--chat-bg-secondary: #f7f7f8;
--chat-bg-tertiary: #ececec;
--chat-bg-hover: #e3e3e3;
--chat-text-primary: #171717;
--chat-text-secondary: #6b6b6b;
--chat-text-tertiary: #8e8e8e;
--chat-text-inverse: #ffffff;
--chat-border-primary: #e3e3e3;
--chat-border-secondary: #ececec;
--chat-accent-primary: #10a37f;
--chat-accent-secondary: #1a7f64;
--chat-success: #047857;
--chat-success-bg: rgba(16, 185, 129, 0.12);
--chat-danger: #b91c1c;
--chat-danger-bg: rgba(239, 68, 68, 0.1);
--chat-warning: #92400e;
--chat-warning-bg: rgba(245, 158, 11, 0.16);
--chat-info: #1d4ed8;
--chat-info-bg: rgba(59, 130, 246, 0.1);
}themeColors is applied as inline custom properties on the chat's root element, so it wins over these rules.
Theme Provider
Chat wraps itself in a ChatThemeProvider. Use the provider yourself when you build a layout from the parts (ChatMessageList, ChatInput, ChatHistory) so they get the same variables, and to change colours at runtime with useChatTheme:
import { ChatThemeProvider, useChatTheme, ChatMessageList, ChatInput, useChat } from "@cognipeer/chat-ui";
function Accent() {
const { setTheme } = useChatTheme();
return <button onClick={() => setTheme({ colors: { accentPrimary: "#e94560" } })}>Rose accent</button>;
}
function Layout() {
const chat = useChat({ baseUrl: "/api/agents", agentId: "assistant" });
return (
<>
<Accent />
<ChatMessageList messages={chat.messages} />
<ChatInput onSend={chat.sendMessage} />
</>
);
}
<ChatThemeProvider defaultMode="dark" theme={{ mode: "dark", colors: { accentPrimary: "#10a37f" } }}>
<Layout />
</ChatThemeProvider>ChatThemeProvider takes defaultMode ("dark" by default) and theme (a Partial<ChatTheme>); setTheme merges what you pass over the current theme, colours key by key. A Chat inside the provider keeps its own provider: change its look with its own props.
Dynamic Theme Switching
theme and themeColors are read once, when Chat mounts. Give the chat a key that changes with the theme to start it afresh. A remount returns the chat to its initial state (the conversation named by conversationId, or a new chat, with an empty composer), so reserve it for a deliberate switch such as a light / dark toggle:
import { useState } from "react";
import { Chat } from "@cognipeer/chat-ui";
function App() {
const [theme, setTheme] = useState<"dark" | "light">("dark");
return (
<div>
<button onClick={() => setTheme((t) => (t === "dark" ? "light" : "dark"))}>
Toggle Theme
</button>
<Chat
key={theme}
baseUrl="/api/agents"
agentId="assistant"
theme={theme}
/>
</div>
);
}Brand Colors
Example with brand colors:
// Blue theme
<Chat
baseUrl="/api/agents"
agentId="assistant"
themeColors={{
accentPrimary: "#3b82f6",
accentSecondary: "#2563eb",
}}
/>
// Purple theme
<Chat
baseUrl="/api/agents"
agentId="assistant"
themeColors={{
accentPrimary: "#8b5cf6",
accentSecondary: "#7c3aed",
}}
/>
// Rose theme
<Chat
baseUrl="/api/agents"
agentId="assistant"
themeColors={{
bgPrimary: "#1a1a2e",
bgSecondary: "#16213e",
accentPrimary: "#e94560",
accentSecondary: "#c73e54",
}}
/>Component-Level Styling
Style the container with className, and the markdown of messages through the .chat-markdown rules:
<Chat
baseUrl="/api/agents"
agentId="assistant"
className="my-chat-container"
/>.my-chat-container {
border-radius: 16px;
overflow: hidden;
}
.my-chat-container .chat-markdown pre {
border-radius: 12px;
}The stylesheet ships a few hooks you can target: .chat-container (the chat root), .chat-markdown (message text), .chat-citation (inline source markers), .chat-scrollbar and .chat-message-actions (the row of buttons under a message). Everything else is Tailwind utility classes that read the --chat-* variables, so changing a variable is the most reliable way to restyle.
Tailwind CSS Integration
Chat UI ships its own compiled stylesheet (@cognipeer/chat-ui/styles.css). If your app also uses Tailwind, the colours are available as chat-* utilities by extending your theme with the same variables:
// tailwind.config.js
module.exports = {
theme: {
extend: {
colors: {
chat: {
bg: { primary: "var(--chat-bg-primary)", secondary: "var(--chat-bg-secondary)" },
accent: { primary: "var(--chat-accent-primary)" },
success: { DEFAULT: "var(--chat-success)", bg: "var(--chat-success-bg)" },
danger: { DEFAULT: "var(--chat-danger)", bg: "var(--chat-danger-bg)" },
},
},
},
},
};Reduced motion and contrast
Spinners, pulses and the message-action fade stop under prefers-reduced-motion: reduce. The light theme's status colours are chosen to stay readable on white; if you override them, keep a contrast ratio of at least 4.5:1 against the matching ...Bg tint. See Accessibility.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
| Colours do not change | Import the stylesheet once (@cognipeer/chat-ui/styles.css), and check that no host style sets the --chat-* variables on a closer element than the chat root. |
theme changed but the chat did not | The theme is read at mount: remount with key. |
borderRadius or fonts in ChatTheme do nothing | Only colors is applied by the provider; use CSS for shape and fonts. |
| The status colours look wrong with a preset | Presets set the twelve base colours only. Add success, danger, warning and info (and their ...Bg) to themeColors if you need different ones. |
| A custom palette makes notices unreadable | Pick warning / danger / info colours with enough contrast against bgPrimary; the tint (...Bg) sits on top of it. |

