# Design tokens

> Every colour, type, space, corner, shadow and motion token a page uses.

The tokens of [the design system](https://docs.0x1audio.com/design-system.md), as Tailwind utilities. A page gets them from the theme, once in its CSS: `@import "@0x1audio/design-system/theme.css"`. [A custom page](https://docs.0x1audio.com/page.md) shows the setup.

## Colour

Use these as Tailwind utilities (`bg-card`, `text-muted-foreground`, `border-border`). They follow light and dark on their own.

| Need | Use | Not |
| --- | --- | --- |
| The page | `bg-background` | |
| A raised surface: a card, a group of settings | `bg-card` with `border-border` | a shadow at rest |
| Something floating: a menu, a dialog, a toast | `bg-popover` with `shadow-lg` (the components do this) | |
| Text | `text-foreground`; `text-muted-foreground` for secondary text; `text-subtle-foreground` for hints, counts and column headers | a grey below 4.5:1 contrast for anything people must read |
| Lines | `border-border`, a hairline; `border-border-strong` for outline buttons and chips | a border where space already separates |
| Hover, pressed, selected | `bg-hover`, `bg-pressed`, `bg-selected` | a different colour for each state |
| Fields | `bg-field`, `hover:bg-field-hover` | |
| Status | `text-destructive`, `text-success`, `text-warning-foreground` | status colours as decoration |
| Focus | the ring every component shows on `:focus-visible` | removing outlines; a coloured ring |
| A waveform's resting bars | `bg-wave` or `fill-wave`; the part already played is ink (`text-foreground`) | a grey from the text scale; colour in a waveform |
| Behind a dialog | `bg-scrim` | |
| Your accent | `accent` in `app.json`, and `<AppHero accent="#20b4c4" />` | your accent on buttons, text or backgrounds below the hero |

**Tints are derived, not new colours.** A status wash is the status token at a fixed opacity: `bg-success/8`, `bg-warning/8` and `bg-destructive/6` for fills, `border-success/25` and the like for lines. `Callout` with a `tone` does this for you.

`bg-chrome` and `lit` belong to Mixel's own window frame and its live indicators, so apps don't use them.

## Type

Figtree, included with the theme. Seven sizes, each with its own line height and tracking:

| Token | Size / line | Use |
| --- | --- | --- |
| `text-display` | 32 / 36 | One hero line, and rarely in an app |
| `text-title` | 22 / 28 | The page title (`PageHeader`, `AppHero`) |
| `text-heading` | 15 / 22 | Section titles, dialog titles, empty-state titles |
| `text-body` | 14 / 20 | Rows, buttons, paragraphs |
| `text-small` | 13 / 18 | Secondary text, menu items, labels, facts lines |
| `text-caption` | 12 / 16 | Meta lines and counts |
| `text-micro` | 11 / 14 | Column headers, menu labels, shortcuts |

- Weights: 400 for body text, 500 for labels and row titles, 600 for headings.
- Numbers that change or line up get `tabular-nums`: lengths, BPM, counts, percentages.
- Sentence case everywhere, and no all-caps labels.
- Tailwind's own sizes (`text-sm`, `text-lg`) and fixed sizes like `text-[13px]` are off the scale.

## Space

A 4 px grid, which is Tailwind's spacing scale. The rhythm that repeats:

- **The page:** `px-8` at the sides. `PageHeader` brings its own space above and below; a `PageSection` is `py-4`, with `gap-2` between its title and its content.
- **Rows:** 40 px (`min-h-10`) for a list of sounds or files, `gap-px` apart. Hover fills separate rows, not dividers.
- **Toolbars:** every control `h-8`, with `gap-2` between them.

Shared widths and grids are tokens, not fixed values: `max-w-dialog`, `max-w-settings`, `max-w-page`, and `grid-cols-(--grid-tiles)`, `grid-cols-(--grid-cards)`, `grid-cols-(--grid-label-value)`. Never `w-[26rem]`.

## Corners

Crisp corners, to sit with Mixel's pixel mark.

| Token | Size | Use |
| --- | --- | --- |
| `rounded-sm` | 4 px | `Kbd`, tiny chips |
| `rounded-md` | 6 px | Rows, small buttons, menu items |
| `rounded-lg` | 8 px | Buttons, fields, tiles, toolbar controls |
| `rounded-xl` | 12 px | Cards, dialogs, popovers, the hero |
| `rounded-full` | | Round play buttons, avatars, switches |
| `rounded-icon` | 22% | App icons only |

Never more than `rounded-xl` on a container.

## Elevation

| Token | Use |
| --- | --- |
| none | Everything at rest on the page. A hairline border is enough. |
| `shadow-xs` | Filled buttons, app icons |
| `shadow-sm` | The active tab of a segmented control; a card lifting on hover, one step only |
| `shadow-md` | A popover close to what opened it |
| `shadow-lg` | Menus, dialogs, toasts |

No coloured shadows, no glows, and no blur as decoration.

## Motion

| Token | Value | Use |
| --- | --- | --- |
| `--duration-fast` | 120 ms | Colour, background, border and press. Built into every component. |
| `--duration` | 180 ms | Entering, cross-fades, menus opening |
| `--duration-slow` | 280 ms | Dialogs, progress bars, larger moves |
| `--ease-out` | | Everything entering or responding |
| `--ease-in-out` | | Something moving between two places |
| `--ease-spring` | | A dialog scaling in, once |

- New content enters with `animate-enter`. A list cascades with `stagger` on the list and `style={{ "--i": index }}` on each item.
- Buttons press to `active:scale-98`. Nothing grows on hover.
- Menus and dialogs open from what opened them, and close faster than they open. The components do this.
- Only live work loops: an indeterminate `Progress`, a `Skeleton`. Nothing loops for decoration.
- Reduced motion is handled by the theme.
