---
title: "Theming"
description: "The @guuey/chat theme is a serializable token object — one schema projected to CSS variables on web and style values on native, with per-token fallbacks."
---

A `@guuey/chat` theme is **data, not CSS** — a serializable token object you can keep in code, in config, or in a database. The React kit projects it as `--guuey-chat-*` CSS custom properties on the transcript root; the [React Native tier](/chat-native/) maps the same object to style values. One schema, two projections.

## The schema

```ts
interface GuueyChatTheme {
  name: string;
  /** BOTH palettes always present — mode is the consumer's runtime choice. */
  colors: { light: GuueyChatPalette; dark: GuueyChatPalette };
  typography: { fontFamily?: string; monoFontFamily?: string; scale?: number };
  shape: {
    radius: "none" | "soft" | "round";
    density: "compact" | "comfortable";
  };
}

interface GuueyChatPalette {
  accent: string; // primary action + user bubble
  onAccent: string; // text on accent
  ink: string; // primary text
  inkMuted: string; // secondary text
  surface: string; // bubbles, cards
  canvas: string; // page background behind the transcript
  canvasMuted: string; // grouped/collapsed rows, wells
  error: string;
}
```

Both palettes are always present in a theme; which one paints is the `mode` prop (`"light"` / `"dark"`) — so switching modes is a runtime choice, never a second theme object.

## The two shipped themes

- **`DEFAULT_CHAT_THEME`** — brand-neutral and polished; what you get without configuring anything. Designed to sit inside _your_ product without announcing guuey.
- **`GUUEY_CHAT_THEME`** — the guuey visual identity (the widget's shipped palette). Use it when you want the surface to read as guuey.

```tsx
import { GUUEY_CHAT_THEME } from "@guuey/chat";

<GuueyChat endpointUrl="…" appId="…" theme={GUUEY_CHAT_THEME} mode="dark" />;
```

## Your own theme

`resolveTheme` merges a partial theme over the default **per token** — supply only what you change:

```tsx
import { resolveTheme, type GuueyChatTheme } from "@guuey/chat";

const brandTheme: GuueyChatTheme = resolveTheme({
  name: "acme",
  colors: {
    light: { accent: "#7c3aed", onAccent: "#ffffff" },
    dark: { accent: "#a78bfa" },
  },
  shape: { radius: "round", density: "compact" },
});

<GuueyChat endpointUrl="…" appId="…" theme={brandTheme} mode="dark" />;
```

Every token you omit falls back to the default theme's value — a half-configured theme can never produce an unreadable surface.

## The evolution promise

The schema is built to be **stored**: parsing is lenient (unknown keys pass through rather than failing), changes to the schema are additive-only, and every new token ships with a default fallback — so a theme object saved today keeps parsing against every future version of the package.

That posture is what makes the theme safe to store as platform data — which is exactly what per-app theme configuration does.

## Theme the hosted surfaces

You can set a theme **once per app** in the console: your app's **Design** page on platform.guuey.com has a theme editor over this same schema, with a live transcript preview, validated on save. Every surface guuey hosts for the app picks it up — the embedded widget, the app's standalone page, and Portal chat — with no redeploy.

The editor lives on the app's Design page in the console; branding and the distribution tabs are covered on the same docs page — [Design & distribution](/console-design/).

Your own `@guuey/chat` mounts are unaffected: the `theme` prop you pass always wins on surfaces you render yourself.

## Fine-grained control

The projection is ordinary CSS custom properties, so anything the token schema doesn't express can be adjusted in your own stylesheet against the kit's class names — or by replacing a [component slot](/chat-customization/#rung-2--component-overrides) entirely. Prefer tokens first: they're what survives schema evolution, and what the console's per-app theme stores.