WP Rig’s design token system (SPEC-017) is the single source of truth for colors, typography, spacing, and breakpoints. You describe your design system once in config/tokens.json, and the framework generates CSS custom properties, theme.json editor presets, and — optionally — a Tailwind config. Dark mode, WCAG contrast auditing, and Figma round-trips are built in.
The mental model
Three ideas anchor the system:
config/tokens.jsonis the seed, not a style store. It describes the default color/type/space system and the editor palette only. Component styles stay in CSS — there is no CSS-as-JSON.- CSS custom properties are the interface. Components read
var(--…). Generated variables live in_tokens.generated.css; your own overrides live in_tokens.custom.css. - Pure-CSS authors are first-class. Prefer hand-editing CSS? Skip
tokens.jsonentirely and write_tokens.custom.css— everything still works.
The files, and which ones you edit
| Path | Role | Edit? |
config/tokens.json | Layered token source (v2) | yes |
config/tokens.schema.json | JSON Schema for editor IntelliSense | no |
assets/css/src/_tokens.generated.css | Generated custom properties (gitignored) | never — run rig:tokens |
assets/css/src/_tokens.custom.css | Hand-authored vars/overrides | yes |
config/theme.custom.json | Hand-authored theme.json fragments | yes |
config/tailwind.custom.js | Hand-authored Tailwind extensions (opt-in) | yes |
tailwind.config.js | Generated shell (opt-in, gitignored) | never |
Generated files are gitignored because their content depends on theme.designTokens.colorBinding and the active paradigm. Regenerate with npm run build or npm run rig:tokens — and note that build:css, dev:css, and lint:css regenerate tokens automatically, so a fresh checkout builds without a manual step.
Token taxonomy: three tiers
- Primitives — raw values with no meaning:
primitives.color.brand.500,primitives.space.base. - Semantic — roles, each with a
light+darkvalue:semantic.surface,semantic.text,semantic.state.danger. - Component (optional) — component-scoped aliases:
component.button.bg.
References use {dotted.path} syntax. Primitives may only be referenced by semantic tokens; semantic tokens never reference other semantic tokens; and semantic colors must declare both light and dark values (unless meta.darkMode is "none").
Naming conventions
| Tier | Pattern | Example |
| Primitive color | --color-{hue}-{step} | --color-brand-500 |
| Semantic color | --color-{role} | --color-surface |
| Space | --space-{step} | --space-base |
| Layout | --layout-{slug} | --layout-content |
| Font family / size | --font-family-{slug} / --font-size-{slug} | --font-family-highlight |
| Breakpoint | --breakpoint-{slug} | --breakpoint-tablet |
| Radius / alpha | --radius-{slug} / --alpha-{slug} | --alpha-weak |
Color math: derive, don’t name
Encode intent, not computation. A token name exists for a role or an axis — never for an intersection. Shades, tints, opacities, and on-colors are derived with CSS math instead of declaring every combination:
/* Alpha from one color token — no --color-accent-50 */
border-color: rgb(from var(--color-accent) r g b / var(--alpha-weak));
/* Tint toward the surface (correct in light AND dark) */
background: color-mix(in oklab, var(--color-accent) 12%, var(--color-surface));
/* Shade for hover/active — derived, not declared */
background: oklch(from var(--color-accent) calc(l - 0.06) c h);
/* Complement without a new token */
color: oklch(from var(--color-accent) l c calc(h + 180));Dark mode
Every semantic token declares a dark value. rig:tokens generates the @media (prefers-color-scheme: dark) block in _tokens.generated.css — do not hand-write dark overrides for semantic tokens. With colorBinding: "wp-preset" the generated dark block is omitted for preset-bound colors (WordPress presets are single-valued; the Site Editor owns dark). meta.darkMode: "none" disables dark generation entirely — the v1→v2 upgrade sets this so your existing hand-authored dark CSS keeps working.
Configuration
All settings live under theme.designTokens in the config chain:
{
"theme": {
"designTokens": {
"enabled": true,
"source": "config/tokens.json",
"colorBinding": "auto",
"legacyAliases": false,
"contrast": {
"level": "AA",
"onFail": "warn"
},
"emit": {
"tailwind": false
}
}
}
}colorBinding decides how semantic variables bind: independent keeps WP Rig’s own namespace; wp-preset aliases --wp--preset--color--* so token colors appear as editor presets. auto resolves by paradigm — classic and universal themes get independent, block-based themes get wp-preset. Classic never emits --wp--preset-- references.
Workflows
Token-driven
Edit config/tokens.json, run rig:tokens, commit the CSS you changed — never the generated files:
npm run rig:tokensFigma-driven
Export Figma Variables to a W3C DTCG JSON (Tokens Studio), import, and round-trip back:
npm run rig:tokens:import -- --from design/figma.tokens.json [--apply]
npm run rig:tokens:export # default: design/tokens.dtcg.jsonUnmapped tokens are reported, never silently dropped.
Site Editor (block-based)
Edit Global Styles in the Site Editor, bake them into config/user-styles.json with rig:bake styles (see Site Editor Bake & Sync), then regenerate. Precedence is tokens < theme.custom.json < user-styles.json, with SSOT keys ($schema, version, settings.viewport, settings.blockVisibility) always owned by tokens.
Tailwind (opt-in)
WP Rig does not ship Tailwind — nothing Tailwind-related is generated by default and no dependency is added. To opt in, install tailwindcss yourself, set theme.designTokens.emit.tailwind: true, and run rig:tokens. That generates config/tailwind.tokens.js and a tailwind.config.js shell, and creates config/tailwind.custom.js for your own extensions (per-key merge, custom wins).
Upgrading a v1 theme to tokens v2
Themes on the old flat tokens.json (top-level colors, typography, spacing, breakpoints) migrate in one reviewed run:
npm run rig:tokens:setup # dry-run: schema + rename plan
npm run rig:tokens:setup -- --apply # writes tokens.json v2 + rewrites usages
npm run rig:tokens # regenerate
npm run lint:css # safety net- Schema: flat keys map into
primitives+semantic, withmeta.darkMode: "none"so existing hand-authored dark CSS keeps working. - Names: a collapse-aware codemod rewrites legacy custom properties (
--content-width→--layout-content,--global-font-color→--color-text,--mobile-breakpoint→--breakpoint-tablet) across CSS, PHP, and JS — never generated files or Gutenberg color slugs. - Backups: every touched file is copied to
.rig-backup/<timestamp>/tokens-setup/before writing. - Idempotent: re-running on a v2 tree is a no-op.
Need a staged rollout? Set theme.designTokens.legacyAliases: true to keep emitting --old: var(--new) aliases while you migrate, then flip it off.
Lint rules and gates
wprig/no-undefined-custom-properties— flagsvar(--x)references not in the token inventory.wprig/no-hardcoded-colors— flags literals that exactly match a token value.- Build gates: schema validation, reference resolution, WCAG contrast, and the classic preset-ref assertion.
The shipped palette has 6 known WCAG AA diagnostics (the gate runs at warn): link hover, accent, quote-citation (dark), and the success/warning/info state colors. They are tracked for a palette pass; flip contrast.onFail to error once your own palette is clean.
Related reading
Pair this with Theme Configuration (the theme.designTokens settings in context), Theme Paradigms (how colorBinding resolves per paradigm), and CSS in WP Rig (the Lightning CSS pipeline that consumes these variables).