Skip to content
Pilotoo
v0.1

Match your brand

You will point the kit’s colours at your own design tokens, so the chat looks like part of your app, and add a light, dark and system switch. The kit styles itself with CSS variables, so a re-theme is a few lines of CSS.

Load tokens.css first, then redefine variables after it. Your rules win by order.

src/styles.css
@import "tailwindcss";
@import "@pilotoo/ui/tokens.css";
@source "../node_modules/@pilotoo/ui/dist";

Start with the accent and the surfaces. Each variable is a plain colour, so you can alias your own tokens.

src/brand.css
:root {
--primary: var(--brand-600);
--primary-hover: var(--brand-700);
--primary-active: var(--brand-700);
--primary-weaker: color-mix(in srgb, var(--brand-600) 8%, transparent);
--primary-weak: color-mix(in srgb, var(--brand-600) 25%, transparent);
--primary-foreground: #ffffff;
--fill-bg-master: var(--surface-page);
--surface-canvas: var(--surface-page);
--surface-card: var(--surface-raised);
}

Import brand.css after styles.css.

tokens.css defines dark in two places: the system preference, unless the page forces light, and an explicit data-theme="dark". Override both, or the chat keeps the kit’s dark colours.

src/brand.css
@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']) {
--primary: var(--brand-400);
--primary-foreground: #101018;
--fill-bg-master: var(--surface-page-dark);
--surface-canvas: var(--surface-page-dark);
--surface-card: var(--surface-raised-dark);
}
}
:root[data-theme='dark'] {
--primary: var(--brand-400);
--primary-foreground: #101018;
--fill-bg-master: var(--surface-page-dark);
--surface-canvas: var(--surface-page-dark);
--surface-card: var(--surface-raised-dark);
}

data-theme="light" or "dark" on the root element forces a scheme. No attribute follows the operating system. applyTheme does exactly that.

src/ThemeSwitch.tsx
import { useState } from 'react';
import { applyTheme } from '@pilotoo/ui/settings';
type Theme = 'light' | 'dark' | 'system';
export function ThemeSwitch() {
const [theme, setTheme] = useState<Theme>('system');
return (
<select
value={theme}
onChange={(event) => {
const next = event.target.value as Theme;
setTheme(next);
applyTheme(next);
}}
>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
);
}

To remember the choice, keep it in your own storage and call applyTheme at startup. The demo web app does this with themeControl from @pilotoo/ui/settings and a themePreferenceStore from @pilotoo/chat-core.

For the embed, pass theme to mountChat. It sets data-theme on the mount element only, and the scoped stylesheet matches it there. To use your own colours in the embed, build the bundle from an app whose stylesheet contains your overrides.

  • Components read only variables such as --primary and --surface-card, surfaced to Tailwind through @theme. Changing a variable re-colours every component that uses it.
  • Light is the base. Dark follows the system until data-theme forces one.
  • applyTheme(preference, root = document.documentElement) accepts any element, which is how a scoped chat gets its own theme.