Skip to content

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.

TierExamplePurpose
Primitive--brand-9, --gray-3, --radiusRaw values. Generated scales, the base radius, durations.
Semantic--primary, --muted-foreground, --ring, --inputPurpose. Maps to primitives, differently per mode.
ComponentVariant classes in buttonVariants, popup-motionDecisions a single component makes from semantic tokens.

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-tooltip

In 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/*.json
pnpm tokens   # regenerate after changing any file in lib/tokens

Exports

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:

public/tokens/light.tokens.json
{
  "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

  1. Add it to the relevant file in lib/tokens.
  2. Run pnpm tokens.
  3. If it's a new utility name (a new type role, shadow or duration), register it in lib/utils.ts so cn() 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.
  4. Document it: the foundations pages render straight from lib/tokens, so most tables update themselves. Add its description in every language to lib/i18n/tokens.ts.