argus review: the literal Catppuccin Latte accent hexes (base08-0F) fail badly as StatusChip fill-text (green/amber/red/yellow on --purple-dim/base03: 1.4:1-3:1, need 4.5:1) and even as plain text on --bg/base00 in the agent/dashboard packages that also import this file (2.3:1-4.8:1). Root cause: Latte's own accents are calibrated against Lattes near-white base/crust, not a mid-gray surface1; the Mocha row does not hit this because Mochas pastel accents are already light, so they contrast fine against a dark surface1 - Lattes saturated-but-mid accents do not have the same headroom against Lattes own lighter surface1. Fix: darken each accent slot from stock Latte (same hue/saturation, lower HSL lightness via binary search) until real WCAG contrast against base03 clears 4.5:1 with margin - verified this also clears >7:1 against base00, so both the swarm-ui chip case and the plain-text case elsewhere are covered by one set of values. base00-07 stay stock Latte (no contrast role, just surfaces/foreground). Yellow and peach read closer to olive/brown than a bright yellow/orange once darkened this far - an inherent property of darkening a warm hue in sRGB, not a mapping mistake; boosting saturation further did not rescue it (checked). Verified with headless-chromium screenshots against the built swarm-ui bundle: StatusChip text (the exact case argus flagged) and every chip tone on /components now read clearly.
170 lines
9.8 KiB
Markdown
170 lines
9.8 KiB
Markdown
# CSS custom properties (theme variables)
|
||
|
||
Colour variables live in **two standalone stylesheets**, split so a theme
|
||
swap touches only the first:
|
||
|
||
1. **`colors.css`** — the 16 `--base00`…`--base0F` base16 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_.
|
||
2. **`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.)
|
||
|
||
`colors.css` ships two rows of the same 16 slots: an unconditional
|
||
Catppuccin Mocha (dark) default, and a Catppuccin-Latte-derived (light)
|
||
row gated behind `@media (prefers-color-scheme: light)` — both use the
|
||
identical slot mapping, so which row is active never changes what a slot
|
||
means. The light row's `base00`-`base07` (surfaces/foreground) are stock
|
||
Latte hexes; `base08`-`base0F` (the eight chromatic slots) are darkened
|
||
from stock Latte to actually clear WCAG AA against how this app uses
|
||
them (real contrast failures found in review — see the comment above
|
||
those declarations in `colors.css` for the numbers and why). This is the
|
||
OS/browser-level default only; it's what a client with no more specific
|
||
theme decision gets (see "Theme swapping" below for how a stylix
|
||
deployment or a future per-user override supersede it).
|
||
|
||
Both are deliberately standalone, not `@import`ed 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):
|
||
|
||
```css
|
||
background: var(--bg-elev);
|
||
border: 1px solid var(--purple-dim);
|
||
```
|
||
|
||
**Hover / active state backgrounds**:
|
||
|
||
```css
|
||
background: var(--border);
|
||
```
|
||
|
||
**Active tab text / accent elements**:
|
||
|
||
```css
|
||
color: var(--purple);
|
||
```
|
||
|
||
**Muted / meta text**:
|
||
|
||
```css
|
||
color: var(--muted);
|
||
```
|
||
|
||
**Error / warning / success badges**:
|
||
|
||
```css
|
||
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.
|
||
|
||
**Two rows, one contract.** `colors.css` ships both a dark default
|
||
(Catppuccin Mocha, unconditional) and a light default (Catppuccin Latte,
|
||
behind `@media (prefers-color-scheme: light)`) — a theme generator that
|
||
overrides the file wholesale (e.g. the stylix path, which writes a single
|
||
unconditional `:root` block with no media query) supersedes both rows at
|
||
once, same as today.
|
||
|
||
`colors.css` base16 slot defaults (Catppuccin Mocha, dark default):
|
||
|
||
| 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`, `--subtext0` have no own slot** — they're
|
||
derived via `color-mix()` over base16 (`--crust` = a darkened `base00`;
|
||
`--muted`/`--subtext0` = `base05`↔`base00` blends). 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 `--cyan` to teal — that's the
|
||
operator's chosen scheme, working as intended.
|
||
- `base04`/`base06`/`base07` aren'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).
|