CSS custom properties (theme variables)
Colour variables live in two standalone stylesheets, split so a theme swap touches only the first:
colors.css— the 16--base00…--base0Fbase16 slots. This is the entire theme swap contract. A generator (e.g. one fed a stylix base16 scheme, which is natively base00–base0F) replaces only this file.theme.css— the semantic layer:--bg,--fg,--purple, … derived from the base16 slots (--bg: var(--base00)etc.). This is what the app references and it never changes on a swap.
Both files live in frontend/packages/shared/src/. Per-page stylesheets
(common.css, dashboard.css, flow.css, logs.css, agent.css)
reference the semantic names and must not redeclare them or reach
for the raw --baseNN slots directly. (base.css holds only the shared
body typography — it references the palette but no longer defines it.)
Both are deliberately standalone, not @imported into the page bundles:
each package re-exports them (src/{colors,theme}.css →
@import "@hive/shared/…") so esbuild emits its own
dist/static/colors.css + dist/static/theme.css, and every page links
them first (colors.css then theme.css) ahead of the page CSS. Note
theme.css does not @import colors.css — that would bake the
slots back into it; they're two separate output files so a swap replaces
just colors.css. (Load order doesn't actually affect resolution —
custom properties resolve at computed-value time — but colors.css is
linked first for clarity.)
Semantic palette (theme.css)
Each semantic var derives from a base16 slot (or, for the three that have
no clean slot, a color-mix() over base16 — pixel-identical under the
default palette). Default hexes shown are Catppuccin Mocha.
| Variable | Derives from | Default hex | Use |
|---|---|---|---|
--bg |
base00 |
#1e1e2e |
page background |
--bg-elev |
base01 |
#181825 |
elevated surfaces: floating dropdowns, popovers |
--border |
base02 |
#313244 |
general borders, hover/active backgrounds |
--purple-dim |
base03 |
#45475a |
subtle borders, terminal chrome, badge backgrounds |
--fg |
base05 |
#cdd6f4 |
primary text colour |
--red |
base08 |
#f38ba8 |
errors, fail state |
--amber |
base09 |
#fab387 |
warnings, pending / running state |
--yellow |
base0A |
#f9e2af |
flash messages, mild warnings |
--green |
base0B |
#a6e3a1 |
success, ok state |
--cyan |
base0C |
#89dceb |
tool-use events, info accents |
--blue |
base0D |
#89b4fa |
links, interactive accent (distinct from cyan) |
--purple |
base0E |
#cba6f7 |
accent — active tabs, links, highlights |
--pink |
base0F |
#f5c2e7 |
thinking events |
--crust |
mix(base00 58%, #000) |
#11111b |
terminal / code block background (below --bg) |
--muted |
mix(base05 55.5%, base00) |
#7f849c |
secondary / dimmed text |
--subtext0 |
mix(base05 77.7%, base00) |
#a6adc8 |
toolbar/status text; dimmer than --fg, lighter than --muted |
Common mistakes
The Catppuccin colour names do not map 1:1 to the variable names. Variables to avoid (undefined — they will silently resolve to transparent / inherited):
| Wrong | Correct |
|---|---|
--text |
--fg |
--mauve |
--purple |
--surface0 |
--bg-elev (float bg) or --border (border/hover) |
--surface1 |
--border |
--surface2 |
--purple-dim |
--overlay0, --overlay1 |
--muted |
--base, --mantle |
--bg, --bg-elev |
Usage guide
Floating menus and dropdowns (e.g. agent context menu, tabbar overflow):
background: var(--bg-elev);
border: 1px solid var(--purple-dim);
Hover / active state backgrounds:
background: var(--border);
Active tab text / accent elements:
color: var(--purple);
Muted / meta text:
color: var(--muted);
Error / warning / success badges:
color: var(--red); /* error */
color: var(--amber); /* warning / running */
color: var(--green); /* ok */
Theme swapping — the base16 contract
The swap interface is colors.css — the 16 base16 slots, not our
semantic names. A theme generator (e.g. one reading a stylix base16
scheme) overrides only colors.css; the semantic layer in theme.css
derives everything else, so the whole UI re-themes with nothing else to
template or regenerate. The base16 slot → semantic mapping is internal
to theme.css (the "Derives from" column above) — a generator never
needs to know our var names, and theme.css + the page bundles stay
untouched.
colors.css base16 slot defaults (Catppuccin Mocha):
| Slot | Default | Standard base16 role | Mapped to |
|---|---|---|---|
base00 |
#1e1e2e |
default bg | --bg, (darkened) --crust |
base01 |
#181825 |
lighter bg | --bg-elev |
base02 |
#313244 |
selection/surface | --border |
base03 |
#45475a |
comments/dim surface | --purple-dim |
base04 |
#585b70 |
dark foreground | (unused; kept for completeness) |
base05 |
#cdd6f4 |
default foreground | --fg, (blended) --muted/--subtext0 |
base06 |
#f5e0dc |
light foreground | (unused) |
base07 |
#b4befe |
lightest | (unused) |
base08 |
#f38ba8 |
red | --red |
base09 |
#fab387 |
orange | --amber |
base0A |
#f9e2af |
yellow | --yellow |
base0B |
#a6e3a1 |
green | --green |
base0C |
#89dceb |
cyan | --cyan (our sky; Catppuccin's base0C is teal) |
base0D |
#89b4fa |
blue | --blue |
base0E |
#cba6f7 |
magenta | --purple |
base0F |
#f5c2e7 |
extra accent | --pink |
Notes for theme authors:
--crust,--muted,--subtext0have no own slot — they're derived viacolor-mix()over base16 (--crust= a darkenedbase00;--muted/--subtext0=base05↔base00blends). They still track a swap automatically; no generator action needed.- A standard Catppuccin base16 scheme uses teal for
base0C; we default it to sky (#89dceb) to preserve the historical accent. A stylix Catppuccin scheme will shift--cyanto teal — that's the operator's chosen scheme, working as intended. base04/base06/base07aren't consumed by a semantic var today; they're kept at their standard values so the 16-slot contract is complete (a partial scheme override still works).