Design tokens and theme.json
A WordPress theme has two places that know what “accent” means, and in Wonderpress they are owned by different projects:
theme.jsondeclares the palette, font families, and type scale. This is what constrains the editor. It is the reason a client’s color picker offers three swatches instead of the spectrum.static/src/scss/lib/tokens/declares the same values as Sass variables for the CSS that actually renders the site. Each assignment is$id: var(--id, fallback).
Left alone, a project types its brand colors into both and they drift. Setting brand colors is the first thing anyone does on a new project, so this is a day-one problem.
The rule
Kit CSS variables are the shared names. theme.json fills them. wonderpress-core prints the join.
Static Kit ships its token files in the shape $color-accent: var(--color-accent, …), with comments saying the host sets matching --color-*, --font-*, and --type-* properties on :root. wonderpress-core is that host. From theme.json it prints:
:root { --color-blue: var(--wp--preset--color--blue); --font-sans-serif: var(--wp--preset--font-family--sans-serif); --type-h2-size-tablet: var(--wp--custom--type--h2--size-tablet); --color-error: var(--wp--custom--color--error);}
So your SCSS consumes $color-blue, the editor offers the Blue swatch, and both resolve to the single value declared in theme.json.
What to declare in theme.json
Three things:
- The palette and font families, in the standard
settings.color.paletteandsettings.typography.fontFamiliesslots. These become editor choices and--wp--preset--*custom properties. - The type scale, under
settings.custom.type: per-role size, line-height, weight, tracking, and breakpoint variants (sizeTabletand so on). WordPress prints these as--wp--custom--type--h2--size-tablet, and the bridge collapses that to the kit name--type-h2-size-tablet. The type scale lives incustombecausesettings.typography.fontSizesis only the editor’s small-to-extra-large dropdown, and WordPress has no preset slot for per-level line-height, weight, or breakpoint variants. - Status colors such as
errorandsuccess, undersettings.custom.color, so they are available to CSS and stay out of the color picker.
Leave the token files as Static Kit stamped them. A project token the kit does not ship, such as $font-mono, belongs in your own token file as var(--font-mono, …) with a matching theme.json slot. Do not put --wp--preset--* names inside Static Kit files; the kit is host-agnostic on purpose, and a reinstall restores the stamp.
The lint contract
wonderpress lint fails in both directions:
- a token file consumes a name
theme.jsondoes not bridge theme.jsonbridges a name no token file consumes
That is what catches a Static Kit release renaming --type-h2-lh before your site silently falls back to a Sass default.
The honest costs
- Sass cannot compute with a custom property.
darken($color-accent, 10%)stops working, because the value does not exist until the browser resolves it.color-mix()covers most of what that was for; where it genuinely does not, declare the derived color as its owntheme.jsonslot rather than reaching back for a literal. - Spacing tokens are not connected. Color, fonts, and the type scale flow through the bridge. Static Kit has no spacing token file, so
settings.spacing.spacingSizesconstrains the editor and stops there.
The alternative (generating theme.json from the SCSS tokens) keeps Sass’s color math intact, and buys a build step plus a theme.json nobody may hand-edit. Wonderpress prefers the subscription until something concrete makes the math worth that trade.
Global Styles
Wonderpress keeps theme.json authoritative by discarding database-backed user Global Styles, which otherwise outrank the file without ever producing a repository diff. A project that intentionally uses the Global Styles UI can opt out:
add_filter( 'wonderpress_strip_user_global_styles', '__return_false' );