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     hues and neutral presets
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:

public/tokens/light.tokens.json
{
  "color": {
    "brand": {
      "9": {
        "$type": "color",
        "$value": {
          "colorSpace": "oklch",
          "components": [0.515, 0.24, 267],
          "hex": "#2c4fee"
        }
      }
    }
  },
  "semantic": {
    "ring": { "$type": "color", "$value": "{color.brand.9}" }
  }
}

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 — the test suite fails until you do.
  4. Document it: the foundations pages render straight from lib/tokens, so most tables update themselves.