Technote

Themes & plugins Practical

Series Planting a design system in WordPress Part 4 of 8

Putting tokens into the theme with CSS custom properties

Declare the tokens in one :root block, have components reference nothing else, and the colour and spacing of the whole site move from a single file.

There are several ways to move tokens into code, but in a WordPress theme CSS custom properties are almost always the right answer, for two reasons: they inherit, and they exist at runtime.

How they differ from preprocessor variables

A SCSS variable is substituted at build time and then gone; the browser never hears the name. A custom property arrives at the browser alive, which means you can redeclare it inside a region and flip everything beneath it.

:root {
  --brand: #0e7c66;
  --brand-hover: #0a5f50;
  --ink: #1f2933;
  --surface: #ffffff;
  --space-4: 16px;
  --radius-md: 8px;
}

.site-button {
  background: var(--brand);
  color: var(--surface);
  padding: var(--space-4);
  border-radius: var(--radius-md);
}

.site-button:hover {
  background: var(--brand-hover);
}

.site-button does not know what the brand colour is, and does not need to. When the rebrand lands, you open the :root block and nothing else.

Scoped redeclaration — the real payoff

Because custom properties inherit, redeclaring tokens inside a region carries every component in it along without touching component code.

.section--inverse {
  --ink: #f7f9fa;
  --surface: #121a24;
  background: var(--surface);
  color: var(--ink);
}

A dark section no longer needs rewritten rules for buttons, cards and badges. Dark mode works the same way, with the redeclaration inside a media query instead of a class. This is where the discipline of referencing only tokens pays you back all at once.

Placing it in the theme

From token to screen — literals exist only in the first box

File layout needs exactly one rule: no colour or spacing literals outside the token file. What makes it a good rule is that it is checkable — one search for values beginning with # in the components directory becomes your review step.

Settle the load order too. If the parent theme stylesheet loads after yours, a specificity war begins and you start bolting importance flags onto individual rules. Load your bundle last and that war never starts; in a child theme this is simply a higher priority number than the parent uses.

Do not lean on fallback values (var(--brand, #0e7c66)). They look like a safety net, but in practice they are a route for literals to creep back into components and they hide the fact that a token is missing. Tokens should always be defined — and when one is not, you want to see it.

The order in which theme structure actually gets tidied is published on our process page, and the audit tools we release for free live on the tools page.

Next part

The tokens are in the theme. The block editor, however, knows nothing about them — editors still get a rainbow picker. The next part opens that channel with theme.json.

More on this topic

All technotes

Themes & plugins Intro

When a custom post type earns its place

Pages are usually enough. But the point where they stop working is quite distinct — and the signal is not how many items you have, it is whether…

Product planners 6 min read

Themes & plugins Intro

Why updates break — and why it is rarely core

If you are postponing because the last update broke something, it is worth asking what actually broke. Naming the cause correctly changes how you prepare for the next…

Site operators 7 min read

₩270,000 · Join the program