# Theming

Every colour, shadow, radius and duration in Eased is a CSS variable. Components read only semantic names, so you re-theme the whole library by restating a few variables. You don't edit any component.

## How the tokens are layered

| Layer | Example | What it's for |
|---|---|---|
| Raw ramps | `--eased-neutral-1` … `--eased-neutral-12`, `--eased-brand-1` … `--eased-brand-12` | Twelve-step OKLCH scales, authored separately for light and dark. Restate these to re-brand. |
| Semantic aliases | `--eased-surface`, `--eased-line-subtle`, `--eased-text-muted`, `--eased-brand` | What a colour is for. Components never read a raw step. |
| Tailwind utilities | `bg-surface`, `border-line-subtle`, `text-text-muted`, `bg-brand` | Generated by `@theme inline` from the aliases, so they follow the theme at runtime. |

Each layer reads only the layer below it. The neutral ramp follows the Radix step roles: 1 app background, 2 subtle background, 3 component background, 4 hover, 5 pressed, 6 subtle border, 7 border, 8 strong border, 9 solid, 10 solid hover, 11 low-contrast text, 12 text.

## Semantic tokens

| Utility | Use it for |
|---|---|
| `bg-bg` | The page canvas |
| `bg-surface`, `bg-surface-sunken`, `bg-surface-raised` | Cards; wells and tracks; popovers and dialogs |
| `bg-surface-hover`, `bg-surface-active` | Hover fill; pressed or selected fill |
| `border-line-subtle`, `border-line`, `border-line-strong` | Dividers and card edges; interactive edges; inputs at rest |
| `text-text`, `text-text-secondary`, `text-text-muted`, `text-text-disabled` | Headings and labels; descriptions; metadata; disabled text |
| `bg-brand`, `text-text-on-brand`, `text-brand-text`, `bg-brand-subtle` | The one primary action; text on it; brand-coloured text; a brand tint |
| `bg-success-subtle` + `text-success-text` (and `warning`, `danger`, `info`) | Status badges. Use solid fills only on buttons and progress. |
| `shadow-resting`, `shadow-raised`, `shadow-floating`, `shadow-overlay` | Resting cards; hover lift; popovers and menus; dialogs |
| `inset-shadow-well`, `inset-shadow-highlight` | Sunken inputs and tracks; the top highlight on primary buttons |

All colours are OKLCH, so steps are evenly spaced in perceived lightness, and a hue change keeps contrast.

## Switch to dark

```html
<html class="dark">
```

Light theme is defined on `:root`. Dark theme is defined on `.dark` (shadcn's convention) and on `[data-theme="dark"]`, so either one works on any ancestor.

Dark is authored, not inverted. Surfaces get lighter as they rise: `surface` is lighter than `bg`, and `surface-raised` is lighter again. Borders sit above their surface. Shadows carry about four times the alpha plus a faint white ring, so a raised card still reads on a dark canvas.

## Change the brand colour

```css
:root {
  --eased-brand-3: oklch(95% 0.03 265);
  --eased-brand-9: oklch(52% 0.2 265);
  --eased-brand-10: oklch(47% 0.2 265);
  --eased-brand-11: oklch(45% 0.16 265);
  --eased-text-on-brand: oklch(98% 0.01 265);
}
.dark {
  --eased-brand-3: oklch(30% 0.06 265);
  --eased-brand-9: oklch(72% 0.15 265);
  --eased-brand-10: oklch(78% 0.13 265);
  --eased-brand-11: oklch(80% 0.11 265);
  --eased-text-on-brand: oklch(18% 0.03 265);
}
```

1. Add the brand steps to your global CSS, after the Eased block.
2. Set your own values for both `:root` and `.dark`.

Components read steps 9 (fill), 10 (fill hover), 11 (text-safe) and 3 (tint) most. The default brand is warm ink in light and cream in dark.

To change the neutrals' tint, set `--eased-neutral-hue`. The default is `80`, a warm tint; around `250` cools them toward blue. The focus ring and selection colour derive from the brand and neutral ramps, so they follow your change.

## Radius roles

| Utility | Value | Use it for |
|---|---|---|
| `rounded-control-xs` | 6px | Chips and badges up to 24px tall |
| `rounded-control-sm` | 8px | Controls 28 to 32px tall |
| `rounded-control` | 10px | Controls 36 to 40px tall |
| `rounded-panel` | 16px | Cards, popovers, menus and toasts |
| `rounded-surface` | 24px | Dialogs, sheets and full-bleed media |
| `rounded-pill` | 9999px | Chips, switches and avatars only. Never a card. |

Components never use a literal radius; they use these roles. To change a role everywhere, restate its variable once, for example `--eased-radius-panel: 12px`.

## Nest rounded boxes

```tsx
<div className="rounded-panel border p-1.5 [--pad:6px] [--radius-outer:var(--eased-radius-panel)]">
  <img className="radius-nested" src="/cover.jpg" alt="" />
</div>
```

When one rounded box sits inside another, the inner radius must equal the outer radius minus the space between them. Otherwise the corners look pinched:

```text
inner radius = outer radius − padding − border width
```

1. On the parent, set `--radius-outer` and `--pad`.
2. On the child, add the `radius-nested` utility.

The child computes its own radius from the two variables, clamped at zero. In the example, the card is 16px with 6px of padding and a 1px border, so the image gets 9px. If you change the card's radius, the image follows.

Every Eased component with a padded shell (Demo Frame, Segmented, Toggle Group, Popover, Hover Card, Composer and Concentric Frame) sets both variables on its root. Anything you put inside gets the right curve without a prop.

If the result is under 2px, raise the outer radius, or clip the child with `overflow: clip` instead of rounding it.

## Control heights

| Utility | Height |
|---|---|
| `h-control-xs` | 24px |
| `h-control-sm` | 32px |
| `h-control-md` | 36px |
| `h-control-lg` | 40px |
| `h-control-xl` | 48px |

Controls share these five heights, so everything in a row lines up. To give a smaller control a 44 by 44px tap target without changing how it looks, add the `hit-area` utility.
