# DESIGN.md — Tidal Design System > **Audience:** AI coding agents and engineers contributing to this repo or consuming it. > **Purpose:** the rules that keep the system coherent. Read this before writing UI code. --- ## 0. Source of truth 1. **Figma** is the design source of truth. File: `N7zMXtdBdI71vQcBW3retD`. 2. **`/design-tokens/`** at the repo root is the canonical token export (DTCG format) — re-exported from Figma via the Tokens plugin. Versioned in git so every clone is reproducible. 3. **`packages/tokens/scripts/build.ts`** ingests `/design-tokens/` and emits everything in `packages/tokens/dist/` (CSS vars, JSON, Tailwind preset). Never edit `dist/` or `src/_generated.ts` by hand. 4. If Figma changes, re-export to `/design-tokens/` and run `pnpm tokens:build`. See `FIGMA_SYNC.md`. --- ## 0.5. Design principles Short list of invariants. When a design-in-Figma or a judgment call conflicts with one of these, push back before you implement. 1. **A heading is never smaller than its content.** Any label, title, or heading that introduces a block must be **≥ the size of the text it introduces** — equal size at minimum, usually one or more steps larger. If you feel tempted to set a heading to `text-sm` above a body of `text-base`, the heading is wrong. Applies to card titles, popover/section headings, form-field labels, legend-style group titles, and every other "title → content" pair. 2. **Hierarchy is read top-down.** The eye lands on the largest thing first. Don't invert that with tiny titles above big bodies — it reads as a label/caption, not a heading. 3. **Default to the calmest variant.** Reach for Primary (button), elevation > 0 (card), or destructive/success tones only when the content genuinely warrants them. See rules §1 and §1a. 4. **Token before hex.** If Figma shows a value, there's a token. If there isn't, add one — never inline a hex in component code (§1, §7). 5. **Compose before extending.** If `Card`, `Input`, `InputGroup`, `Popover` exist, use them. Don't reinvent a surface with a `
` (§5). 6. **Accessibility is part of "done".** Keyboard nav, focus-visible rings, `aria-*` wiring. See §5, §6. ## 1. Token taxonomy There are **three layers**. Always reach for the highest layer that fits. | Layer | Where | When to use | |---|---|---| | **1. Primitives** | `Tailwind Colors`, `Liquid colors` (Lilac/Viola/Mandarin), `Tailwind Primitives` — exposed via `liquid` export and Tailwind palette | Almost never in components. Reference these only when defining a semantic token in Figma. | | **2. Semantic** | `--background`, `--foreground`, `--card`, `--popover`, `--border-default`, `--border-focus`, `--input`, `--accent`, `--ring`, `--button-{primary,secondary,destructive,success,default}` (+ `-foreground`, `-hover`), `--sidebar-*`, plus gradients (`--gradient-button-*`) and rings (`--ring-{primary,destructive}`). | **Default choice for all component code.** | | **3. Component** | Add a CSS var scoped to the component when a value doesn't generalize. | When a component has a tweak that does not generalize. | **Specials**: - `--background-destructive-fill-10`, `--background-success-fill-10` (and `-hover-20`) — faded backgrounds for callouts, secondary destructive surfaces. - `--neon-green` (`#adfa1d`) — accent for special highlights only. ### Hard rules - ❌ **Never** write a raw hex (`#cd82f0`) in component code or MDX. - ❌ **Never** reference Tailwind palette ramps (`bg-zinc-100`) in components — go through a semantic token. - ❌ **Never** inline `style={{ color: '...' }}` for design values. - ✅ Use `bg-primary text-primary-foreground` (Tailwind preset) **or** `var(--primary)` in raw CSS. - ✅ When a value is missing, **add a semantic token** rather than reaching down a layer. --- ## 2. Light & dark mode contract - Every semantic token has a `light` and `dark` value in `packages/tokens/src/semantic.ts`. - The `dark` mode is activated by `class="dark"` on `` (Tailwind convention). - Components must work in both modes **without conditionals**. If you find yourself writing `dark:` overrides on a token-driven class, the token is wrong — fix the token. - ⚠️ **Current state**: dark values in `semantic.ts` are seeded from shadcn defaults. They have NOT yet been pulled from Figma. Sync them when the dark frame is ready (see `FIGMA_SYNC.md`). --- ## 2.4. Disabled state **One strategy: 50% opacity via class.** Every disabled control (and any descendants) dims uniformly. Applied via the twin classes `disabled:opacity-50 data-[disabled]:opacity-50` so it fires for both native `