# Theming

The token contract behind the foundation: color pairs, spacing steps, and radius.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. An agent skill is available at [/.well-known/agent-skills/site-skill.md](/.well-known/agent-skills/site-skill.md).



The foundation is token-driven. Colors are CSS variables mapped to Tailwind utilities; spacing and radius are compiled from a TypeScript token scale. Nothing in a component hardcodes a hex value or a raw utility.

## Color tokens [#color-tokens]

Colors follow the shadcn semantic-pairs contract: every surface token has a matching foreground token, declared as CSS variables on `:root` and bridged into Tailwind v4 through `@theme inline`:

```css
@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
}

:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.4386 0 0);
}
```

The pairs the primitives consume:

| Pair                                 | Consumed by                             |
| ------------------------------------ | --------------------------------------- |
| `background` / `foreground`          | `Shell`, `Section`, `Box` base surfaces |
| `card` / `card-foreground`           | `Box surface="card"`                    |
| `muted` / `muted-foreground`         | `Section surface="muted"`, `Box`        |
| `primary` / `primary-foreground`     | `Section surface="primary"`, `Box`      |
| `secondary` / `secondary-foreground` | `Section surface="secondary"`, `Box`    |

To re-theme, re-declare the variables in your own stylesheet after the base theme — components pick up the new values without any code changes. Inversion is consumption, not configuration: `Section surface="inverted"` renders `bg-foreground text-background`, so one palette powers both directions.

## Spacing [#spacing]

Spacing is a TypeScript scale, not CSS variables. `lib/tokens.ts` defines the steps `"0"`, `"0.5"`, `"1"`, `"2"`, `"2.5"`, `"3"`, `"4"`, `"5"`, `"6"`, `"7"`, `"8"`, `"9"`, `"10"`, `"12"`, `"14"`, `"16"`, `"20"`, `"24"` — the Tailwind spacing scale — and generates the class maps the layout family spreads into its variants:

```ts
export const gapClasses = spacingClasses("gap");
// { "0": "gap-0", "0.5": "gap-0.5", ..., "12": "gap-12", ..., "24": "gap-24" }
```

When you write `<Stack gap="12">`, the prop keys the map and the component renders `gap-12`. One step equals 4 pixels; `sizeTokens` exposes the pixel values for layout math. Because the keys are Tailwind step names, no arbitrary-value utilities are generated and the compiled CSS stays static.

## Radius [#radius]

One base variable derives the whole scale by offset, so changing a single value re-scales every radius utility:

```css
@theme inline {
  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
}
```

`Box radius` accepts the compiled keys: `"none"`, `"sm"`, `"md"`, `"lg"`, `"xl"`, `"2xl"`, `"3xl"`, `"4xl"`.

## Class discovery [#class-discovery]

Tailwind v4 scans source files to know which utilities exist. The token-generated classes are composed at build time inside `lib/tokens.ts`, so make sure your content configuration sees that file:

```css
@source "../**/*.{ts,tsx}";
```

If your scanner cannot see the module (for example, you consume a built dist), `tokens.ts` also exports `_safelist`: a space-joined string of every generated utility for `@source inline(...)`.

## Dark mode [#dark-mode]

All color tokens are CSS variables. To support dark mode, re-declare them under a `.dark` class (and Tailwind's dark variant of your choice) — borders and overlays usually work best as alpha whites in dark. Wiring a theme switcher is up to your application; the registry ships tokens, not a provider.
