# Theming

Every pixilui item reads the same tokens, defined once in `foundation.css`. You theme the library by changing tokens, never by editing items.

## How the tokens are organised

- **Raw tokens** are CSS custom properties named `--pixilui-*` (for example `--pixilui-surface`, `--pixilui-radius-control`). The prefix keeps them apart from shadcn's `--background` and `--accent`, which mean something else.
- **Utilities** map the raw tokens into Tailwind v4 through `@theme inline`. Items use only the utilities: `bg-surface`, `text-secondary`, `border-border`, `rounded-control`, `h-control-md`, `text-14`, `shadow-raised`, `duration-fast`.

| Group | Utilities |
| --- | --- |
| Surfaces | `bg-background`, `bg-surface`, `bg-surface-raised`, `bg-surface-muted`, `bg-surface-inverted` |
| Text | `text-foreground`, `text-secondary`, `text-muted`, `text-inverted` |
| Borders | `border-border`, `border-border-subtle`, `border-border-strong`, and `hairline` (a 1px overlay that never changes a measurement) |
| Accent | `bg-accent-solid`, `text-accent-contrast`, `text-accent-strong`, `bg-accent-subtle` |
| Status | `bg-success`, `text-danger`, `bg-warning-subtle`, `text-info-foreground`, and the same for each of success, warning, danger, info |
| Radii | `rounded-control` (18px), `rounded-panel` (26px), `rounded-surface` (34px), `rounded-pill` |
| Control heights | `h-control-sm` (36px), `h-control-md` (44px), `h-control-lg` (50px), also as `w-` and `size-` |
| Type | `text-12`, `text-14`, `text-16`, `text-18`, `text-22`, `text-28`, `text-36`, `text-52`, `text-72`, with line height and tracking included; `font-display`, `font-body`, `font-mono` |
| Shadows | `shadow-resting`, `shadow-raised`, `shadow-floating` |
| Motion | `duration-instant` to `duration-spring`, `ease-enter`, `ease-smooth-out`, `ease-exit`, `ease-move`, `ease-swap` |

Two names differ from the role names on purpose. The accent fill is `bg-accent-solid` and text on it is `text-accent-contrast`, because shadcn uses `bg-accent` and `text-accent-foreground` for the grey hover in its menus. pixilui leaves those alone, so your existing shadcn components keep their look.

The type scale (`text-12` to `text-72`) is separate from Tailwind's `text-sm` and `text-xl`, so installing pixilui never shifts your typography.

## Dark mode

Light is the default. Dark applies under `[data-theme="dark"]` or the `.dark` class, on `<html>` or on any element:

```html
<html data-theme="dark">
```

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

The `.dark` class is what next-themes and shadcn apps already set, so pixilui follows your existing toggle. There is no `prefers-color-scheme` default: your app decides. Dark values are tuned separately, not inverted: surfaces step up in lightness as they rise, and text roles keep 4.5:1 contrast on their surfaces.

Because the theme is read where it is used, you can nest themes. A dark preview inside a light page, or a light card inside a dark sidebar, renders correctly:

```tsx
<div data-theme="dark">
  <ApprovalCard {...props} />
</div>
```

<!-- demo: theme-pair -->

## Accents

Eight accents ship with the foundation. Set one with `data-accent` on any element; without it the accent is neutral (near black in light, near white in dark). Pick a row to try it on the button.

<!-- demo: accents -->

| Accent | Light fill | Dark fill |
| --- | --- | --- |
| `neutral` | `oklch(0.2 0 0)` | `oklch(0.94 0 0)` |
| `violet` | `oklch(0.54 0.21 290)` | `oklch(0.56 0.2 290)` |
| `blue` | `oklch(0.56 0.225 263)` | `oklch(0.56 0.2 263)` |
| `green` | `oklch(0.53 0.13 155)` | `oklch(0.72 0.15 155)` |
| `amber` | `oklch(0.8 0.16 78)`, dark text | `oklch(0.82 0.15 80)`, dark text |
| `orange` | `oklch(0.56 0.17 42)` | `oklch(0.72 0.16 48)`, dark text |
| `coral` | `oklch(0.56 0.17 28)` | `oklch(0.72 0.15 30)`, dark text |
| `rose` | `oklch(0.55 0.2 8)` | `oklch(0.56 0.2 8)` |

<!-- /demo -->

```html
<body data-accent="violet">
```

Each accent has four roles: the fill (`bg-accent-solid`, used by primary buttons, selection and switches), text on the fill (`text-accent-contrast`), accent-coloured text and icons (`text-accent-strong`), and a tinted background (`bg-accent-subtle`). Light fills stay dark enough for white text to clear 4.5:1; amber and the lighter dark fills switch to dark text instead.

The accent is also the focus colour: `focus-visible:focus-ring` draws a 2px outline in the accent with a 2px offset.

## Overriding roles

To change a role everywhere, override its raw token after the foundation import. Set light values on `:root` and dark values under your dark selector:

```css
:root {
  --pixilui-background: oklch(0.985 0.004 90);
  --pixilui-radius-control: 12px;
  --pixilui-radius-panel: 18px;
  --pixilui-radius-surface: 24px;
}

[data-theme="dark"],
.dark {
  --pixilui-background: oklch(0.15 0.004 90);
}
```

Keep the radii nested (control smaller than panel, panel smaller than surface), so a control inside a panel inside a surface still looks concentric.

To use your brand colour as the accent, set `--pixilui-accent` and its three partners. These win over `data-accent` in both themes:

```css
:root {
  --pixilui-accent: oklch(0.55 0.2 145);
  --pixilui-accent-strong: oklch(0.48 0.18 145);
  --pixilui-accent-subtle: oklch(0.96 0.03 145);
  --pixilui-accent-foreground: oklch(0.99 0 0);
}
```

To keep separate light and dark brand values, override the pairs instead (`--pixilui-accent-light`, `--pixilui-accent-dark`, and the same for `-strong`, `-subtle` and `-foreground`), and the theme in force picks one.

## Restyling one item

Items expose their state as data attributes (`data-status`, `data-loading`, `data-risk`), and every item takes `className`, merged last. Target the attributes from your own CSS or with Tailwind's `data-*` variants instead of editing the item:

```tsx
<ApprovalCard className="data-[risk=high]:shadow-floating" {...props} />
```
