# Theming and tokens

> Theme both kits like shadcn/ui: the same variables (primary, secondary, muted, accent, destructive, border, ring, radius, charts, sidebar) restyle every component, light and dark.

Both kits use shadcn/ui's theme variables. A theme is any CSS that sets them, so a theme from ui.shadcn.com/themes, tweakcn or your own design system works as it is, and every component follows it: buttons, fields, menus, tables, charts, the Sidebar, in Halo and in Pro, light and dark.

```css
@import "@twentyfirstmedia/pro/styles.css";

:root {
  --primary: oklch(0.546 0.245 262.881);
  --primary-foreground: oklch(0.985 0 0);
  --ring: oklch(0.546 0.245 262.881);
  --radius: 0.75rem;
}
.dark,
[data-theme="dark"] {
  --primary: oklch(0.707 0.165 254.624);
  --primary-foreground: oklch(0.145 0 0);
}
```

The kits define their variables with zero specificity, so your theme wins in any order. Dark mode follows `data-theme="dark"` or the `.dark` class on `<html>` (next-themes works as it is), and `data-theme="system"` follows the operating system.

## The variables

| Variable | Used for |
| --- | --- |
| `--background`, `--foreground` | Page background and default text |
| `--card`, `--card-foreground` | Cards, panels, fields, the workspace sheet |
| `--popover`, `--popover-foreground` | Menus, popovers, select lists, command palette, hover cards, dialogs |
| `--primary`, `--primary-foreground` | Primary buttons, checked boxes and radios, switches, selected chips and days, progress, current marks |
| `--secondary`, `--secondary-foreground` | Soft buttons and icon buttons |
| `--muted`, `--muted-foreground` | Quiet fills (tracks, search fields, code) and secondary text |
| `--accent`, `--accent-foreground` | The highlight under the pointer or keyboard: menu items, rows, options, nav items |
| `--destructive`, `--destructive-foreground` | Danger buttons, error text and icons |
| `--border`, `--input`, `--ring` | Hairlines, field and checkbox borders, focus rings |
| `--radius` | The base corner radius; every corner derives from it |
| `--chart-1` … `--chart-5` | Chart series (lines, dots, legends) |
| `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring` | The Sidebar |

The kits' own names (`--canvas`, `--surface`, `--ink`, `--line`, Pro's `--bg-000` … `--fg-400` scale) point at these, and the in-between shades are mixed from them, so you only set the variables above. Outside the theme on purpose: the five tints and status colours (they carry meaning), Halo's dark app frame, paper (e-mail and invoice previews) and media overlays. Halo draws chart bars in soft fills of the series colours (`--chart-sky` … `--chart-blush`); the theme builder and `twentyfirst-ui theme` set them for you when the charts change.

## Presets, CLI and builder

Six brand presets keep each kit's neutrals and change the brand colour: blue, violet, emerald, orange, rose and teal.

```css
@import "@twentyfirstmedia/pro/styles.css";
@import "@twentyfirstmedia/pro/themes/violet.css";
/* both kits loaded: @twentyfirstmedia/ui-core/themes/violet.css */
```

The CLI writes a theme into your app and imports it: a preset, a brand colour (light, optionally dark) or a shadcn theme file.

```sh
npx @twentyfirstmedia/ui theme violet
npx @twentyfirstmedia/ui theme "#2563eb,#60a5fa"
npx @twentyfirstmedia/ui theme ./shadcn-theme.css
```

The [theme builder](https://ui.twentyfirst.media/pro/themes/) shows a theme live on real components and gives you the CSS.

## Themes at runtime

For a brand per customer or a colour the user picks, `UIProvider` takes the same variables. One set applies in both modes; `{ light, dark }` picks by the current theme.

```tsx
import { UIProvider, brandTheme } from "@twentyfirstmedia/pro";

<UIProvider tokens={{ primary: tenant.color, ring: tenant.color }}>{children}</UIProvider>;
<UIProvider tokens={brandTheme("#2563eb", "#60a5fa")}>{children}</UIProvider>;
```

`themeCss(theme, { kit })` writes the CSS for a theme (for a server-rendered `<style>`), `parseThemeCss(css)` reads a shadcn theme, and `brandTheme(color)` picks readable text for a brand colour.

## Glass

For UI that floats over a map, a 3D scene, a video or photos, surfaces can be glass: translucent, blurred and colour-lifted over what is behind, with light catching the top edge. Halo's glass is soft and milky, Pro's is dark with a hairline edge.

```tsx
import { FloatingLayout, MapView, Panel, SearchField, UIProvider } from "@twentyfirstmedia/pro";

// a layout for UI over a background: four slots, glass by default
<FloatingLayout
  height={720}
  background={<MapView background={<YourMap />} points={places} height="100%" />}
  top={<SearchField placeholder="Search places" />}
  start={<Panel title="Layers">…</Panel>}
  end={<Panel title="Harbour Museum">…</Panel>}
/>;

// one surface
<Panel surface="glass" title="Layers">…</Panel>;

// everything floating in a subtree: panels, cards, dock, map legend, menus, popovers, dialogs
<UIProvider scope="subtree" surface="glass">…</UIProvider>;
```

- Inside a glass region, top-level Panels, Cards, the Dock and map legends and controls turn into glass, and so do loose controls (search field, chips, icon and secondary buttons, segmented controls). Menus, popovers, dialogs and drawers take a stronger fill because they carry more text. A surface nested in a glass surface stays clear instead of stacking glass on glass; `surface="solid"` opts one out.
- What sits on glass follows it: inputs, tab tracks, badges, hover and selection highlights turn translucent too.
- The fills keep body text at 4.5:1 or more over any backdrop. Reduced transparency and forced colours switch to solid surfaces, more contrast to the stronger fill, and browsers without backdrop blur get the stronger fill too.
- The glass tokens are themable like every other colour: `--glass`, `--glass-strong`, `--glass-control`, `--glass-well`, `--glass-hover`, `--glass-border`, `--glass-highlight`, `--glass-sheen`, `--shadow-glass` and `--glass-filter` (the backdrop filter). By default they derive from `--card` and `--foreground`, so a theme carries over.
- Without React: `class="tf-glass"` on a surface, or `data-surface="glass"` on any container.

See the [map pattern](https://ui.twentyfirst.media/pro/patterns/MapScreen/) for a full screen.

### Glass dashboards

A whole app can be glass: put `Ambient` behind it and wrap it in `<UIProvider surface="glass">`. The `AppShell` turns into glass too (top bar, rail, sidebar; the sheet clears so the background shows through), and tinted tiles (`StatTile tint="sky"`, tinted Cards) lay their tint over the glass instead of an opaque block.

```tsx
import { Ambient, AppShell, StatTile, UIProvider } from "@twentyfirstmedia/pro";

<UIProvider surface="glass">
  <Ambient fixed />
  <AppShell brand="Bloom">
    <StatTile value="7h 42m" label="Sleep" tint="sky" />
    …
  </AppShell>
</UIProvider>;
```

`Ambient` draws soft colour fields from `--ambient-1…4` (pastels in Halo, a faint aurora in Pro) that drift slowly; `colors` takes your own, `src` a photo or (with `video`) a video under a calming scrim, and children your own layer. The drift stops with motion off and pauses off screen. With `fixed`, keep `body` transparent so the background shows. On glass, the faintest text steps up one shade so secondary text stays readable. See the [wellbeing pattern](https://ui.twentyfirst.media/pro/patterns/WellbeingScreen/).

## Tailwind

The preset maps the variables to shadcn's utility names, so `bg-primary text-primary-foreground`, `bg-muted text-muted-foreground`, `border-border`, `ring-ring` and `bg-sidebar` work, plus the kits' own names (`bg-surface`, `text-ink`).

## Light, dark and density

Theme and density are attributes: `data-theme="light|dark|system"` and `data-density="compact|comfortable"` on `<html>` or any subtree, or the `theme` and `density` props of `UIProvider`. Halo follows the system theme and is comfortable by default; Pro is dark and compact by default.

```tsx
<UIProvider theme="dark" density="compact">…</UIProvider>
```

## Layout and breakpoints

The kits are mobile first: every component works from a 320px wide screen up, and from 1181px on the full desktop layout applies. Phones (up to 760px) get one column: the shell stacks, rail and page nav become scrolling rows, the dock sticks to the bottom, tables scroll inside their box, dialogs open as bottom sheets and popovers stay on screen. Tablets (761–1180px) move asides below the main column. On touch screens text fields use 16px so browsers do not zoom in, and a compact kit (Pro) switches to comfortable hit targets unless `data-density` is set.

For your own grids, `tf-grid` takes its columns from custom properties: `--cols` on desktop, `--cols-md` on tablets (defaults to `--cols`) and `--cols-sm` on phones (defaults to one column).

```tsx
<div
  className="tf-grid"
  style={{ "--cols": "repeat(4, minmax(0, 1fr))", "--cols-sm": "1fr 1fr" } as React.CSSProperties}
>
  <StatTile value="18" label="Projects" />
  …
</div>
```

## Control sizes

Every control sits on one height scale: Halo 32 / 40 / 48px (sm / md / lg), Pro 24 / 28 / 34px. Mix sizes only on purpose; a row of controls uses one size.

## Colour

| Token | Halo | Pro | Use |
| --- | --- | --- | --- |
| `--background` | `#f4f3f0 / #0f0f10` | `#f6f6f7 / #09090b` | Theme: page background behind every panel (shadcn `background`). |
| `--foreground` | `#141414 / #f2f1ed` | `#111114 / #ededf0` | Theme: default text and icons (shadcn `foreground`). |
| `--card` | `#ffffff / #1a1a1c` | `#ffffff / #0e0e11` | Theme: cards, panels, inputs and the workspace sheet (shadcn `card`). |
| `--card-foreground` | `{foreground}` | `{foreground}` | Theme: text on cards. |
| `--popover` | `{card}` | `{card}` | Theme: menus, popovers, select lists, command palette, hover cards, dialogs. |
| `--popover-foreground` | `{foreground}` | `{foreground}` | Theme: text on popovers. |
| `--primary` | `#141414 / #f2f1ed` | `#111114 / #ededf0` | Theme: the brand colour. Primary buttons, checked boxes, switches, selected chips and days, progress, active marks. |
| `--primary-foreground` | `#ffffff / #141414` | `#ffffff / #09090b` | Theme: text and icons on `primary`. |
| `--primary-hover` | `{primary}` | `color-mix(in oklab, {primary} 70%, #000000) / color-mix(in oklab, {primary} 70%, #ffffff)` | Theme: `primary` under the pointer. |
| `--secondary` | `#ecebe7 / #232326` | `#f0f0f2 / #16161a` | Theme: secondary actions. Soft buttons and icon buttons, neutral chips and badges. |
| `--secondary-foreground` | `{foreground}` | `{foreground}` | Theme: text on `secondary`. |
| `--muted` | `#ecebe7 / #232326` | `#f0f0f2 / #16161a` | Theme: quiet fills. Tracks of segmented controls, tabs and progress, search fields, code, placeholders. |
| `--muted-foreground` | `#5f5e5a / #a9a8a3` | `#4f4f58 / #a6a6b0` | Theme: secondary text, captions, metadata. |
| `--accent` | `#ecebe7 / #232326` | `#f0f0f2 / #16161a` | Theme: highlight under the pointer or keyboard. Menu items, list rows, options, ghost buttons, nav items. |
| `--accent-foreground` | `{foreground}` | `{foreground}` | Theme: text on `accent`. |
| `--destructive` | `#8a2b23 / #f2a8a0` | `#b0301f / #ef6f61` | Theme: destructive actions and errors. Danger buttons, error text and icons. |
| `--destructive-foreground` | `{card}` | `{card}` | Theme: text on solid `destructive`. |
| `--border` | `#e5e4df / #2c2c30` | `#dfdfe3 / #26262c` | Theme: hairlines and dividers between surfaces. |
| `--input` | `#a8a7a1 / #5a5a60` | `#8a8a94 / #62626d` | Theme: borders of fields, checkboxes and radios, and strong lines. |
| `--ring` | `#2f6fe0 / #8fb8ff` | `{foreground}` | Theme: keyboard focus rings. |
| `--chart-1` | `{sky-ink}` | `{sky-ink}` | Theme: chart series 1 (sky series). |
| `--chart-2` | `{mint-ink}` | `{mint-ink}` | Theme: chart series 2 (mint series). |
| `--chart-3` | `{butter-ink}` | `{butter-ink}` | Theme: chart series 3 (butter series). |
| `--chart-4` | `{lilac-ink}` | `{lilac-ink}` | Theme: chart series 4 (lilac series). |
| `--chart-5` | `{blush-ink}` | `{blush-ink}` | Theme: chart series 5 (blush series). |
| `--sidebar` | `{card}` | `{card}` | Theme: Sidebar background. |
| `--sidebar-foreground` | `{foreground}` | `{foreground}` | Theme: Sidebar text. |
| `--sidebar-primary` | `{primary}` | `{primary}` | Theme: Sidebar emphasis (current item mark, badges). |
| `--sidebar-primary-foreground` | `{primary-foreground}` | `{primary-foreground}` | Theme: text on `sidebar-primary`. |
| `--sidebar-accent` | `{accent}` | `{accent}` | Theme: Sidebar item under the pointer and the current item. |
| `--sidebar-accent-foreground` | `{accent-foreground}` | `{accent-foreground}` | Theme: text on `sidebar-accent`. |
| `--sidebar-border` | `{border}` | `{border}` | Theme: Sidebar edges and dividers. |
| `--sidebar-ring` | `{ring}` | `{ring}` | Theme: focus rings inside the Sidebar. |
| `--canvas` | `{background}` | `{bg-000}` | Page background behind every panel and card. |
| `--surface` | `{card}` | `{bg-100}` | Cards, panels, inputs and the main workspace sheet. |
| `--surface-sunken` | `{muted}` | `{bg-200}` | Search fields, segmented-tab tracks, quiet chips and icon-button fills on `surface`. |
| `--frame` | `#121212 / #050505` | `{bg-100}` | The dark app frame: top bar, dock and the outer shell around the workspace. |
| `--on-frame` | `#f4f3f0 / #f2f1ed` | `{fg-100}` | Text and icons on `frame`. |
| `--on-frame-muted` | `#a3a29d` | `{fg-300}` | Secondary text and idle nav icons on `frame` (7:1+). |
| `--ink` | `{foreground}` | `{fg-100}` | Headings, numbers and body copy on `surface`, `canvas`, `surface-sunken` and every tint. |
| `--ink-muted` | `{muted-foreground}` | `{fg-200}` | Descriptions, meta lines and labels on `surface` and `canvas` (4.5:1+ in both themes). |
| `--ink-subtle` | `color-mix(in oklab, {muted-foreground} 91%, {background}) / color-mix(in oklab, {muted-foreground} 84%, {background})` | `{fg-300}` | Placeholders, disabled text and the quietest meta; 4.5:1 on surface and surface-sunken in both themes, still clearly lighter than ink-muted. |
| `--line` | `{border}` | `{line-200}` | Hairline dividers and table rules; decorative, never the only boundary of a control. |
| `--line-strong` | `{input}` | `{line-300}` | Dashed placeholder tiles and input borders (3:1 on `surface`). |
| `--sky` | `#cfe3fb / #1e3550` | `{info-bg}` | Info tint: stat tiles, event cards, the 'scheduled' state. Text on it is `ink`. |
| `--sky-ink` | `#1f4f8a / #9cc6f7` | `{info}` | Icons and labels on `sky` or as an icon colour on `surface`. |
| `--mint` | `#d6efc3 / #24401a` | `{ok-bg}` | Success tint: published/live states, positive stat tiles. Text on it is `ink`. |
| `--mint-ink` | `#2f5d16 / #a8db86` | `{ok}` | Icons and labels on `mint`; success marks on `surface`. |
| `--lilac` | `#e9cdf4 / #3d2547` | `{signal-bg}` | Highlight tint: the one featured card in a grid, lesson events. Text on it is `ink`. |
| `--lilac-ink` | `#6a2a82 / #dba6ee` | `{signal}` | Icons and labels on `lilac`. |
| `--butter` | `#fbe7aa / #4a3a10` | `{warn-bg}` | Money and attention tint: revenue tiles, task events, pending states. Text on it is `ink`. |
| `--butter-ink` | `#6b4d00 / #f3d374` | `{warn}` | Icons and labels on `butter`. |
| `--blush` | `#f8d6d2 / #4a2320` | `{danger-bg}` | Danger and support tint: errors, tickets, destructive confirms. Text on it is `ink`. |
| `--blush-ink` | `#8a2b23 / #f2a8a0` | `{danger}` | Icons and labels on `blush`; destructive text on `surface`. |
| `--focus` | `{ring}` | `{ring}` | Focus ring: 2px solid outline, 1px offset, on every interactive element. |
| `--count` | `#d6efc3` | `{signal}` | Count pills on nav items and tabs (unread, open approvals). |
| `--on-count` | `#141414` | `{bg-000}` | Text on `count`. |
| `--on-frame-success` | `#a8db86` | `{ok}` | Success marks on frame: the brand dot, ok lines in logs. |
| `--on-frame-danger` | `#f2a8a0` | `{danger}` | Error lines in logs and other danger marks on frame. |
| `--scrim` | `rgba(10, 10, 10, 0.32)` | `rgba(17,17,20,0.36) / rgba(0,0,0,0.62)` | Backdrop behind dialogs, sheets and the command palette. |
| `--scrim-strong` | `rgba(10, 10, 10, 0.38)` | `{scrim}` | Backdrop behind modals and the command palette. |
| `--glass` | `color-mix(in srgb, {card} 72%, transparent) / color-mix(in srgb, {card} 70%, transparent)` | `color-mix(in srgb, {card} 74%, transparent) / color-mix(in srgb, {card} 72%, transparent)` | Glass surfaces: panels, cards, docks and map overlays floating over content. Opaque enough that body text stays at 4.5:1 or more over any backdrop. |
| `--glass-strong` | `color-mix(in srgb, {card} 86%, transparent)` | `color-mix(in srgb, {card} 88%, transparent)` | Glass for text-heavy floating layers: menus, popovers, dialogs, drawers. |
| `--glass-control` | `color-mix(in srgb, {card} 62%, transparent) / color-mix(in srgb, {foreground} 10%, transparent)` | `color-mix(in srgb, {card} 55%, transparent) / color-mix(in srgb, {foreground} 7%, transparent)` | Inputs, secondary buttons and chips on glass (replaces surface inside a glass surface). |
| `--glass-well` | `color-mix(in srgb, {foreground} 6%, transparent)` | `color-mix(in srgb, {foreground} 5%, transparent)` | Tab tracks, badges, search fields and nested cards on glass (replaces surface-sunken). |
| `--glass-hover` | `color-mix(in srgb, {foreground} 8%, transparent) / color-mix(in srgb, {foreground} 12%, transparent)` | `color-mix(in srgb, {foreground} 7%, transparent) / color-mix(in srgb, {foreground} 9%, transparent)` | Hover and selection highlight on glass (replaces accent). |
| `--glass-border` | `rgba(255, 255, 255, 0.6) / rgba(255, 255, 255, 0.1)` | `color-mix(in srgb, {foreground} 12%, transparent) / color-mix(in srgb, {foreground} 14%, transparent)` | The hairline edge of a glass surface. |
| `--glass-highlight` | `rgba(255, 255, 255, 0.95) / rgba(255, 255, 255, 0.18)` | `rgba(255, 255, 255, 0.7) / rgba(255, 255, 255, 0.07)` | Light catching the top edge of a glass surface. |
| `--glass-sheen` | `rgba(255, 255, 255, 0.5) / rgba(255, 255, 255, 0.05)` | `transparent` | Pro glass is flat: no sheen. |
| `--ambient-base` | `{background}` | `{background}` | Ground of the Ambient background, under the colour fields. |
| `--ambient-1` | `color-mix(in oklab, {sky} 72%, {sky-ink}) / {sky}` | `color-mix(in oklab, {chart-1} 22%, {background}) / color-mix(in oklab, {chart-1} 30%, {background})` | First colour field of the Ambient gradient (a faint aurora in Pro). |
| `--ambient-2` | `color-mix(in oklab, {mint} 72%, {mint-ink}) / {mint}` | `color-mix(in oklab, {chart-4} 18%, {background}) / color-mix(in oklab, {chart-4} 26%, {background})` | Second colour field of the Ambient gradient. |
| `--ambient-3` | `color-mix(in oklab, {lilac} 72%, {lilac-ink}) / {lilac}` | `color-mix(in oklab, {chart-2} 16%, {background}) / color-mix(in oklab, {chart-2} 22%, {background})` | Third colour field of the Ambient gradient. |
| `--ambient-4` | `color-mix(in oklab, {butter} 72%, {butter-ink}) / {butter}` | `color-mix(in oklab, {foreground} 6%, {background}) / color-mix(in oklab, {foreground} 8%, {background})` | Fourth colour field of the Ambient gradient. |
| `--ambient-scrim` | `rgba(255, 255, 255, 0.28) / rgba(10, 10, 10, 0.45)` | `rgba(246, 246, 247, 0.4) / rgba(9, 9, 11, 0.55)` | Softens a photo or video under glass so the page keeps its calm. |
| `--bg-000` | `{canvas}` | `{background}` | Canvas: app background, behind hairline grids, inputs on surfaces. |
| `--bg-100` | `{surface}` | `{card}` | Surface: every grid cell, panel, table, sidebar, dialog. |
| `--bg-200` | `{surface-sunken}` | `{muted}` | Raised: row and button hover, kbd keys, skeletons, code blocks. |
| `--bg-300` | `{line}` | `color-mix(in oklab, {foreground} 3%, {muted}) / color-mix(in oklab, {foreground} 5%, {muted})` | Selected: active nav item, selected row, pressed segment, open menu trigger. |
| `--fg-100` | `{ink}` | `{foreground}` | Primary text and icons on every surface (bg-000 to bg-300). |
| `--fg-200` | `{ink-muted}` | `{muted-foreground}` | Secondary text: descriptions, table cells that are not the row's key, inactive nav items. On bg-000 to bg-300. |
| `--fg-300` | `{ink-muted}` | `color-mix(in oklab, {muted-foreground} 92%, {background}) / color-mix(in oklab, {muted-foreground} 90%, {background})` | Muted text: meta, timestamps, column headers, eyebrows. On bg-000 to bg-300. |
| `--fg-400` | `{ink-subtle}` | `color-mix(in oklab, {muted-foreground} 47%, {background}) / color-mix(in oklab, {muted-foreground} 57%, {background})` | Disabled text and placeholders only. Below 4.5:1 by design; never for information. |
| `--line-100` | `{line}` | `color-mix(in oklab, {border} 59%, {card}) / color-mix(in oklab, {border} 52%, {card})` | Faint hairline: row separators inside tables and lists, background grid. |
| `--line-200` | `{line}` | `{border}` | Hairline: the structural 1px grid between cells, panel and overlay borders, table header rule. Decorative, not a control boundary. |
| `--line-300` | `{line-strong}` | `{input}` | Control border (3:1 on bg-000, bg-100, bg-200): inputs, checkboxes, secondary buttons, crosshair marks. |
| `--chart-grid` | `{line}` | `{line-100}` | Chart gridlines. |
| `--chart-sky` | `{sky} / {sky-ink}` | `{fg-100}` | Chart series tinted sky: a step of the monochrome ramp (charts carry no decorative colour). |
| `--chart-mint` | `{mint} / {mint-ink}` | `color-mix(in oklab, {foreground} 38%, {card}) / color-mix(in oklab, {foreground} 48%, {card})` | Chart series tinted mint: a step of the monochrome ramp (charts carry no decorative colour). |
| `--chart-lilac` | `{lilac} / {lilac-ink}` | `{fg-300}` | Chart series tinted lilac: a step of the monochrome ramp (charts carry no decorative colour). |
| `--chart-butter` | `{butter} / {butter-ink}` | `{fg-200}` | Chart series tinted butter: a step of the monochrome ramp (charts carry no decorative colour). |
| `--chart-blush` | `{blush} / {blush-ink}` | `{line-300}` | Chart series tinted blush: a step of the monochrome ramp (charts carry no decorative colour). |
| `--ramp-1` | `color-mix(in oklab, {chart-1} 12%, {background})` | `color-mix(in oklab, {chart-1} 12%, {background})` | Map and heatmap ramp, step 1 of 5 (lightest): calm sequential colours for values such as price per m² or density, mixed from `chart-1` and the background. |
| `--ramp-2` | `color-mix(in oklab, {chart-1} 28%, {background})` | `color-mix(in oklab, {chart-1} 28%, {background})` | Map and heatmap ramp, step 2 of 5 (middle): calm sequential colours for values such as price per m² or density, mixed from `chart-1` and the background. |
| `--ramp-3` | `color-mix(in oklab, {chart-1} 46%, {background})` | `color-mix(in oklab, {chart-1} 46%, {background})` | Map and heatmap ramp, step 3 of 5 (middle): calm sequential colours for values such as price per m² or density, mixed from `chart-1` and the background. |
| `--ramp-4` | `color-mix(in oklab, {chart-1} 66%, {background})` | `color-mix(in oklab, {chart-1} 66%, {background})` | Map and heatmap ramp, step 4 of 5 (middle): calm sequential colours for values such as price per m² or density, mixed from `chart-1` and the background. |
| `--ramp-5` | `color-mix(in oklab, {chart-1} 88%, {background})` | `color-mix(in oklab, {chart-1} 88%, {background})` | Map and heatmap ramp, step 5 of 5 (strongest): calm sequential colours for values such as price per m² or density, mixed from `chart-1` and the background. |
| `--ok` | `#2f5d16 / #a8db86` | `#177447 / #5cc28f` | Success / done / healthy: text and status shapes (circle). On bg-000 to bg-300. |
| `--ok-bg` | `#d6efc3 / #24401a` | `rgba(23,116,71,0.09) / rgba(92,194,143,0.12)` | Tint behind ok badges, banners and diff additions. |
| `--info` | `#1f4f8a / #9cc6f7` | `#2856b0 / #7aa7f5` | Running / in progress / neutral notices. Shape: pulsing dot. |
| `--info-bg` | `#cfe3fb / #1e3550` | `rgba(40,86,176,0.08) / rgba(122,167,245,0.12)` | Tint behind info badges and banners. |
| `--warn` | `#6b4d00 / #f3d374` | `#765600 / #d9b44a` | Warning: budget at 80 %, expiring domains, degraded checks. Shape: triangle. |
| `--warn-bg` | `#fbe7aa / #4a3a10` | `rgba(118,86,0,0.09) / rgba(217,180,74,0.12)` | Tint behind warning badges and banners. |
| `--danger` | `{destructive}` | `{destructive}` | Error, failed run, destructive actions, budget stop. Shape: square. |
| `--danger-bg` | `#f8d6d2 / #4a2320` | `rgba(176,48,31,0.08) / rgba(239,111,97,0.12)` | Tint behind error badges, banners, diff removals. |
| `--signal` | `#6a2a82 / #dba6ee` | `#9a4807 / #f2994a` | Needs a human: approval waiting, beacons, the approval count. Shape: diamond. The only hue that may draw the eye unprompted. |
| `--signal-bg` | `#e9cdf4 / #3d2547` | `rgba(154,72,7,0.09) / rgba(242,153,74,0.14)` | Tint behind approval cards' header and signal badges. |
| `--idle` | `#5f5e5a / #a9a8a3` | `{fg-300}` | Status idle: ready, inactive (ring). Alias of `ink-muted`. |
| `--diff-add` | `#2f5d16 / #a8db86` | `{ok}` | Added lines in override diffs. |
| `--diff-add-bg` | `#d6efc3 / #24401a` | `{ok-bg}` | Fill of added lines. Alias of `mint`. |
| `--diff-del` | `#8a2b23 / #f2a8a0` | `{danger}` | Removed lines in override diffs. |
| `--diff-del-bg` | `#f8d6d2 / #4a2320` | `{danger-bg}` | Fill of removed lines. Alias of `blush`. |
| `--paper` | `#ffffff` | `#ffffff` | Document previews (invoices, PDFs): paper is white in both themes. |
| `--paper-ink` | `#141414` | `#111114` | Text on paper. |
| `--frame-hover` | `rgba(255, 255, 255, 0.08)` | `{bg-200}` | Hover fill on the dark frame (top bar, dock, code blocks). Same in both themes. |
| `--frame-active` | `rgba(255, 255, 255, 0.12)` | `{bg-300}` | Active or selected fill on the dark frame. Same in both themes. |
| `--frame-strong` | `rgba(255, 255, 255, 0.18)` | `{bg-300}` | Strong fill or divider on the dark frame. Same in both themes. |
| `--on-frame-strong` | `#ffffff` | `{fg-100}` | Highlighted text on the frame (log highlights). Same in both themes. |
| `--on-frame-warn` | `#f3d374` | `{warn}` | Warnings and numbers in logs and code on the frame. Same in both themes. |
| `--on-frame-signal` | `#d4b8f0` | `{signal}` | Keywords in code on the frame. Same in both themes. |
| `--ink-hover` | `rgba(0, 0, 0, 0.06)` | `{bg-200}` | Hover fill on tinted surfaces such as alerts. Same in both themes. |
| `--hairline` | `rgba(0, 0, 0, 0.12)` | `{line-200}` | Thin outline around swatches and colour wells. Same in both themes. |
| `--media-scrim` | `rgba(10, 10, 10, 0.6)` | `rgba(10, 10, 10, 0.6)` | Labels and counters on top of photos. Same in both themes. |
| `--media-ink` | `#ffffff` | `#ffffff` | Text and lines on top of photos. Same in both themes. |
| `--media-shadow` | `rgba(0, 0, 0, 0.3)` | `rgba(0, 0, 0, 0.3)` | Shadow behind lines drawn on photos. Same in both themes. |
| `--lightbox` | `rgba(8, 8, 8, 0.92)` | `rgba(8, 8, 8, 0.92)` | Full-screen image viewer background. Same in both themes. |
| `--lightbox-ink` | `rgba(255, 255, 255, 0.8)` | `rgba(255, 255, 255, 0.8)` | Captions in the image viewer. Same in both themes. |
| `--paper-muted` | `#706f6a` | `#706f6a` | Secondary text on paper (4.5:1 on white). |
| `--paper-subtle` | `#6b6a65` | `#6b6a65` | Small print on `paper`. Same in both themes. |
| `--serp-ink` | `#202124` | `#202124` | Search ad preview: text. Same in both themes. |
| `--serp-muted` | `#4d5156` | `#4d5156` | Search ad preview: description and URL. Same in both themes. |
| `--serp-link` | `#1a0dab` | `#1a0dab` | Search ad preview: headline link. Same in both themes. |
| `--serp-chip` | `#f1f3f4` | `#f1f3f4` | Search ad preview: favicon chip. Same in both themes. |

## Shadow

| Token | Halo | Pro | Use |
| --- | --- | --- | --- |
| `--shadow-card` | `0 1px 2px rgba(20,20,20,0.04), 0 6px 20px rgba(20,20,20,0.05) / 0 1px 2px rgba(0,0,0,0.4)` | `none` | Resting cards. Pro: none, hairlines divide. |
| `--shadow-lift` | `0 2px 4px rgba(20,20,20,0.06), 0 14px 32px rgba(20,20,20,0.10) / 0 2px 6px rgba(0,0,0,0.5), 0 14px 32px rgba(0,0,0,0.45)` | `none` | Hovered card. Pro: none. |
| `--shadow-float` | `0 16px 40px rgba(0,0,0,0.22) / 0 16px 40px rgba(0,0,0,0.6)` | `0 16px 40px rgba(17,17,20,0.12) / 0 16px 40px rgba(0,0,0,0.55)` | Floating layers: dock, toasts, menus and dialogs. |
| `--shadow-overlay` | `{shadow-float}` | `0 16px 40px rgba(17,17,20,0.12) / 0 16px 40px rgba(0,0,0,0.55)` | Only floating layers: menus, popovers, dialogs, toasts, command palette. Always paired with a line-200 border. |
| `--shadow-glass` | `0 1px 2px rgba(20, 20, 40, 0.06), 0 12px 40px rgba(20, 20, 40, 0.14) / 0 1px 2px rgba(0, 0, 0, 0.3), 0 16px 48px rgba(0, 0, 0, 0.45)` | `0 8px 24px rgba(17, 17, 20, 0.1) / 0 12px 32px rgba(0, 0, 0, 0.5)` | Shadow under glass surfaces. |

## Component knobs

| Token | Halo | Pro | Use |
| --- | --- | --- | --- |
| `--button-radius` | `{radius-pill}` | `{radius-1}` | Corners of buttons (Button, IconButton). One knob for a theme's button shape. |
| `--field-radius` | `{radius-pill}` | `{radius-1}` | Corners of inputs and selects. |
| `--badge-radius` | `{radius-pill}` | `{radius-1}` | Corners of badges and chips. |
| `--panel-bg` | `{surface}` | `{bg-100}` | Background of a panel: a cell of the hairline grid, a section, a side column. |
| `--panel-border` | `transparent` | `{line-200}` | Border colour of a panel standing on its own; transparent where panels float. |
| `--panel-radius` | `{radius-lg}` | `{radius-0}` | Corners of a panel. Pro: square cells of the hairline grid. |
| `--panel-shadow` | `{shadow-card}` | `none` | Resting shadow of a panel. Pro: none, the hairlines divide. |
| `--panel-pad` | `{space-6}` | `{space-4}` | Inner padding of a panel. |
| `--panel-gap` | `{space-4}` | `1px` | Gap between panels in a grid: the hairline in Pro, air between cards in Halo. |
| `--glass-filter` | `blur(24px) saturate(180%) brightness(1.05) / blur(24px) saturate(160%) brightness(0.85)` | `blur(16px) saturate(130%) brightness(1.04) / blur(16px) saturate(120%) brightness(0.8)` | backdrop-filter of glass surfaces: blur, colour lift and a nudge towards the theme's side. |
| `--glass-tint` | `62%` | `40%` | How strongly a tint (StatTile, tinted Card) colours the glass it sits on. |

## Spacing

| Token | Halo | Pro | Use |
| --- | --- | --- | --- |
| `--space-0-5` | `2px` | `2px` | Icon-to-badge nudges, hairline offsets. |
| `--space-1` | `4px` | `4px` | Gap inside badges and kbd groups. |
| `--space-2` | `8px` | `8px` | Gap between icon and label, between buttons in a group. |
| `--space-3` | `12px` | `12px` | Compact horizontal cell and control padding. |
| `--space-4` | `16px` | `16px` | Grid cell padding, comfortable control padding, page gutter on mobile. |
| `--space-5` | `20px` | `20px` | Dialog body padding. |
| `--space-6` | `24px` | `24px` | Cell padding on overview grids; background-grid pitch. |
| `--space-8` | `32px` | `32px` | Gap between stacked sections inside a panel. |
| `--space-10` | `40px` | `40px` | Outer margin of the app frame on large screens. |
| `--space-12` | `48px` | `48px` | Empty-state padding. |
| `--space-16` | `64px` | `64px` | Login and onboarding vertical rhythm. |

## Radius

| Token | Halo | Pro | Use |
| --- | --- | --- | --- |
| `--radius` | `16px` | `2px` | Theme: the base corner radius; Pro is square at 2px, larger values round cards and menus too (shadcn `radius`). |
| `--radius-sm` | `calc(var(--radius) * 0.625)` | `{radius-1}` | Inputs inside cells, row hovers, small tiles. |
| `--radius-md` | `var(--radius)` | `calc((var(--radius) - 2px) * 1)` | Stat cells, event cards, menus: square in Pro. |
| `--radius-lg` | `calc(var(--radius) * 1.375)` | `calc((var(--radius) - 2px) * 1.375)` | Cards and panels: square cells of the hairline grid. |
| `--radius-xl` | `calc(var(--radius) * 1.875)` | `calc((var(--radius) - 2px) * 1.875)` | The workspace sheet and app frame: square in Pro. |
| `--radius-pill` | `999px` | `{radius-1}` | Buttons, chips, badges, tabs and inputs: 2px in Pro. |
| `--radius-0` | `0px` | `0px` | Default for everything structural: cells, panels, tables, dialogs, sheets, cards. |
| `--radius-1` | `{radius-sm}` | `var(--radius)` | Controls: buttons, inputs, badges, kbd, switches, tooltips. |
| `--radius-full` | `9999px` | `9999px` | Only status dots and radio buttons. |

## Motion

| Token | Halo | Pro | Use |
| --- | --- | --- | --- |
| `--dur-fast` | `120ms` | `{duration-fast}` | Press, hover colour changes, chip toggles. |
| `--dur-base` | `220ms` | `{duration-base}` | Tab indicator slide, menu open, card lift. |
| `--dur-enter` | `520ms` | `{duration-base}` | Entrances: short and quiet in Pro. |
| `--dur-slow` | `900ms` | `{duration-base}` | Count-ups, chart draw-ins and progress fills: short in Pro. |
| `--stagger-step` | `55ms` | `0ms` | No staggered entrances in Pro. |
| `--dur-exit` | `160ms` | `{duration-fast}` | Overlays leaving: menus, popovers, dialogs, drawers, toasts. |
| `--motion-time` | `1` | `1` | Multiplier for choreography delays (when the dock, side panels and chart data come in). |
| `--motion-travel` | `1` | `1` | Multiplier for how far things travel when they come in. |
| `--motion-blur` | `0px` | `0px` | Blur things come out of as they rise (expressive Halo only). |
| `--ease-out` | `cubic-bezier(0.22, 1, 0.36, 1)` | `{ease-standard}` | Entrances. |
| `--ease-spring` | `cubic-bezier(0.34, 1.56, 0.64, 1)` | `{ease-standard}` | No overshoot in Pro: the standard curve. |
| `--ease-in-out` | `cubic-bezier(0.65, 0, 0.35, 1)` | `{ease-standard}` | Things moving between two resting places. |
| `--duration-fast` | `{dur-fast}` | `90ms` | Hover and press feedback. |
| `--duration-base` | `{dur-base}` | `140ms` | Menus, popovers, sheets, toasts entering. |
| `--ease-standard` | `{ease-out}` | `cubic-bezier(0.2, 0, 0, 1)` | The only easing curve. |

## Sizes

| Token | Halo | Pro | Use |
| --- | --- | --- | --- |
| `--control-sm` | `32px` | `24px` | Small buttons in dense toolbars. |
| `--control-md` | `40px` | `{control-dense}` | Default control height (follows density in components). |
| `--control-lg` | `48px` | `{control-comfy}` | Large controls. |
| `--row-dense` | `40px` | `28px` | Row height for tables, lists and nav items in compact density. |
| `--row-comfy` | `52px` | `36px` | Row height in comfortable density. |
| `--control-dense` | `{control-sm}` | `28px` | Button and input height in compact density. |
| `--control-comfy` | `{control-md}` | `34px` | Button and input height in comfortable density. |
| `--topbar-h` | `60px` | `44px` | Top bar height. |
| `--sidebar-w` | `44px` | `232px` | Sidebar width, expanded. |
| `--sidebar-w-collapsed` | `44px` | `52px` | Sidebar width, icon-only. |
| `--subnav-w` | `208px` | `{sidebar-w}` | SubNav width. |
| `--side-panel-w` | `304px` | `320px` | Side panel width. |
| `--icon-sm` | `14px` | `14px` | Icons inside badges and dense table cells. |
| `--icon-md` | `16px` | `16px` | Default Lucide icon size. |
| `--icon-lg` | `20px` | `16px` | Icons in navigation: same as icon-md in Pro. |
| `--line-width` | `1px` | `1px` | Every border and grid line (Pro calls it hairline). |

