Requirement
Reference colour only through the product's semantic colour tokens. No raw hex, rgb, hsl, or named colours in UI code. Functional colours (success/warning/danger/info) resolve to Radix Colors scales (COL-2); brand moments resolve to the product's declared primary or its ramp (COL-1) — through tokens, not literals.
Where the values come from
The product's DESIGN.md defines the colour tokens. Where it defines none, the
codebase's own theme tokens (the custom properties in its global stylesheet) decide.
Where neither does, shadcn's semantic tokens (--background, --foreground,
--primary, and the rest) apply. This site's DESIGN.md, for example, points to
app/globals.css, which maps shadcn's semantic names onto the site's own tokens.
Rationale
This is the control that stops inter-session drift — the "new blue" problem, where each generation invents a slightly different value and inconsistency compounds silently. Every deviation we don't make from the declared tokens is consistency we get for free. Where a product declares none, the fallback stack is deliberately boring and AI-legible (Base UI + Radix + shadcn defaults), a vocabulary agents already know.
Passes when
- All colour in changed files resolves through the token layer
(
var(--...)/ token-mapped utility classes). - A genuinely missing token is raised as a finding in the plan, not improvised inline.
Fails when
- A literal like
#0064FF,rgb(37, 99, 235), orslate-ishcustom values appear in component code — even when the value happens to equal a token. - Tailwind palette utilities bypass the semantic layer (
bg-blue-600instead of the mapped semantic class/variable). - Functional states use ad-hoc greens/reds instead of Radix scale tokens.
How to verify
checks/token-audit.py: scan changed files for raw colour patterns; exit 1 with
file/line and the nearest token suggestion. Waiver: documented — named approver in
the decision record (e.g. a marketing surface intentionally outside the product token
set).