Technote

Themes & plugins Practical

Series Planting a design system in WordPress Part 5 of 8

What theme.json does: handing your palette to the block editor

theme.json is not a stylesheet, it is a channel. It hands your palette to the editing screen so editors choose from your system instead of a free colour wheel.

Planting tokens in CSS does not tell the block editor anything. An editor selects a paragraph, opens the colour control, and gets a colour wheel with no relationship to your theme. The file that closes this gap is theme.json.

The name makes it sound like a stylesheet. From a design-system point of view it is really a channel — the one that carries your palette, type sizes and spacing scale into the editing screen.

What handing over a palette produces

{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "settings": {
    "color": {
      "palette": [
        { "slug": "brand",   "name": "Brand",   "color": "#0e7c66" },
        { "slug": "ink",     "name": "Body",    "color": "#1f2933" },
        { "slug": "surface", "name": "Surface", "color": "#ffffff" }
      ]
    }
  }
}

That single file produces three things at once: those colours appear as swatches in the editor, classes such as .has-brand-color become available on the front end, and WordPress emits CSS variables of its own in the form --wp--preset--color--brand.

One file arriving in the editing screen and on the front end at the same time

Keeping the value in one place

There is a trap here. If the same colour is written both in your CSS token file and in theme.json, one day only one of them gets updated — and the mismatch shows up only on screens editors built, so it is found late.

The fix is to make one side depend on the other. Since WordPress emits the preset variables for you, treating theme.json as the source and pointing your tokens at it leaves a single origin.

:root {
  --brand: var(--wp--preset--color--brand);
  --ink: var(--wp--preset--color--ink);
}

The reverse direction is possible, but editor swatches need a real colour value to preview, so this direction is usually the less awkward of the two. Either way the rule is the same: the value lives in exactly one place.

Where theme.json does not reach

It is worth knowing this file’s range precisely. It governs block-built content firmly, but it does not touch hand-coded page templates, headers or footers. Those remain the job of your CSS tokens.

So the practical arrangement is two layers: code and tokens draw the site’s frame, while theme.json fences the content area editors fill. When both layers read the same values, the page reads as one design.

Spacing and type sizes travel the same way (settings.spacing.spacingSizes and settings.typography.fontSizes). Move the scale from part three into those and editorial spacing choices come inside the scale too.

More on how the block editor and themes relate lives in the Themes & plugins archive, and having the theme structure sorted alongside other work is covered by our optimization program.

Next part

The palette is handed over — but the free colour wheel is still sitting next to your swatches. The next part closes it: the settings that trade freedom for consistency.

More on this topic

All technotes

Themes & plugins Practical

Decide the editable regions before you design them

A design the CMS cannot express stays up for negotiation long after it ships. Deciding what editors may change, first, removes the negotiation entirely.

Designers 6 min read

₩270,000 · Join the program