Skip to content

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:

tsx
// 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:

tsx
<Chat
  baseUrl="/api/agents"
  agentId="assistant"
  theme="dark"
  themeColors={{
    bgPrimary: "#1a1a2e",
    bgSecondary: "#16213e",
    bgTertiary: "#0f3460",
    textPrimary: "#eaeaea",
    textSecondary: "#a0a0a0",
    accentPrimary: "#e94560",
  }}
/>

Available Color Properties ​

PropertyDescriptionDefault (Dark)Default (Light)
bgPrimaryMain background#212121#ffffff
bgSecondarySecondary background (sidebar, assistant messages)#171717#f7f7f8
bgTertiaryTertiary background (inputs, chips)#2f2f2f#ececec
bgHoverHover state background#3f3f3f#e3e3e3
textPrimaryPrimary text#ececec#171717
textSecondarySecondary text#b4b4b4#6b6b6b
textTertiaryTertiary text#8e8e8e#8e8e8e
textInverseText on accent backgrounds#171717#ffffff
borderPrimaryPrimary border#3f3f3f#e3e3e3
borderSecondarySecondary border#2f2f2f#ececec
accentPrimaryPrimary accent (buttons, focus)#10a37f#10a37f
accentSecondarySecondary accent#1a7f64#1a7f64
success, successBgApproved / answered outcomes: icon colour and tinted background#34d399, rgba(16, 185, 129, 0.12)#047857, rgba(16, 185, 129, 0.12)
danger, dangerBgError banner, denied outcomes, invalid input, "Limit reached" badge#f87171, rgba(239, 68, 68, 0.12)#b91c1c, rgba(239, 68, 68, 0.1)
warning, warningBgExpired 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, infoBgInformational 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:

tsx
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}
/>
ModePreset ids
Darkmidnight, ocean, purple-haze, sunset, emerald, onyx, nord
Lightchatgpt-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:

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

css
.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:

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

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

tsx
// 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:

tsx
<Chat
  baseUrl="/api/agents"
  agentId="assistant"
  className="my-chat-container"
/>
css
.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:

javascript
// 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 ​

SymptomCause and fix
Colours do not changeImport 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 notThe theme is read at mount: remount with key.
borderRadius or fonts in ChatTheme do nothingOnly colors is applied by the provider; use CSS for shape and fonts.
The status colours look wrong with a presetPresets 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 unreadablePick warning / danger / info colours with enough contrast against bgPrimary; the tint (...Bg) sits on top of it.

Next Steps ​

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub