Design tokens
How prfct's tokens are structured, named, generated and exported as CSS variables, Tailwind utilities and W3C design-token JSON.
Tokens are the contract between design and code. prfct's tokens are defined once, as data, and everything else — CSS, Tailwind utilities, design-tool files and this documentation — is generated from them. Nothing can drift, because nothing is written twice.
Architecture
Tokens come in three tiers. Each tier only references the tier below it.
Components use semantic tokens wherever a role exists and scale steps where a component needs a specific role (a badge's bg-brand-3 text-brand-11). They never use raw values.
Naming
--{scale}-{step} --brand-9, --gray-12, --red-3
--{scale}-contrast --amber-contrast (text on step 9)
--{role} --background, --primary, --border, --ring
--{role}-foreground --primary-foreground, --muted-foreground
--duration-{name} --duration-fast, --duration-spring
--ease-{name} --ease-enter, --ease-spring
--z-{layer} --z-popover, --z-tooltipIn Tailwind, every token is a utility: bg-brand-3, text-muted-foreground, rounded-xl, shadow-floating, ease-spring, duration-fast, z-popover, text-heading-md.
Source of truth
lib/color/oklch.ts OKLCH ↔ sRGB, gamut mapping, WCAG and APCA contrast
lib/color/scale.ts 12-step scale generator with solved text steps
lib/tokens/palette.ts hue and neutral presets, the theme config
lib/tokens/theme.ts theme config → resolved scales, override CSS
lib/tokens/semantic.ts semantic roles → scale steps, per mode
lib/tokens/foundations.ts typography, radius, shadows, motion, layers
scripts/build-tokens.ts → styles/tokens.css + public/tokens/*.jsonpnpm tokens # regenerate after changing any file in lib/tokensExports
Colors are exported in OKLCH with an sRGB hex fallback, so design tools that don't speak OKLCH still get exact values. The default brand is monochrome, so brand.9 is the near-black of gray.12:
{
"color": {
"brand": {
"9": {
"$type": "color",
"$value": {
"colorSpace": "oklch",
"components": [0.228, 0, 0],
"hex": "#1c1c1c"
}
}
}
},
"semantic": {
"ring": { "$type": "color", "$value": "{color.brand.focus}" }
}
}Adding a token
- Add it to the relevant file in
lib/tokens. - Run
pnpm tokens. - If it's a new utility name (a new type role, shadow or duration), register it in
lib/utils.tssocn()merges it correctly. For a type role the test suite fails until you do; shadows, durations, easings and layers have no such check, so add those by hand. - Document it: the foundations pages render straight from
lib/tokens, so most tables update themselves. Add its description in every language tolib/i18n/tokens.ts.