WordPress Development

A Practical Guide to theme.json: Controlling Design in Modern WordPress Themes

A Practical Guide to theme.json: Controlling Design in Modern WordPress Themes

When I build block themes for clients, theme.json is where most of the design system lives. Colours, fonts, font sizes, spacing, layout widths and which options editors can change are all defined in one file. Get it right, and the whole site stays consistent while editors still have enough freedom to build pages comfortably. Get it wrong, and editors face dozens of confusing options, or the site slowly fills with mismatched colours and random font sizes.

theme.json was introduced in WordPress 5.8 and has grown significantly since. It’s now central to how block themes work, and classic themes can use parts of it too. This guide explains how it works in practice.

What theme.json does

theme.json serves two purposes:

  • Settings control what options are available in the editor: which colours appear in the palette, which font sizes editors can choose, whether custom colours are allowed and much more.
  • Styles define how things look by default: background and text colours, typography for the site and for specific blocks, spacing and element styles like links and buttons.

WordPress reads theme.json and generates CSS custom properties, also called CSS variables, and styles for the front end and the editor. That means your design tokens are available consistently in both places, so what editors see matches what visitors see.

Basic structure

A minimal theme.json looks like this:

The schema line enables autocomplete and validation in code editors like VS Code, which makes writing theme.json much easier. The version number refers to the theme.json format version. Version 3 was introduced in WordPress 6.6.

Colour settings

Define a colour palette for your brand:

Each colour becomes a CSS variable, such as –wp–preset–color–primary, which you can use anywhere in your CSS.

The other settings control flexibility. custom set to false removes the colour picker, so editors can only use your palette. defaultPalette set to false hides WordPress’s default colours. For brand consistency, I almost always disable custom colours and default palettes on client sites.

Name colours by role, not appearance

Use slugs like primary, contrast and base rather than blue or dark-grey. If the brand colour changes from blue to green later, a slug named blue becomes confusing. Role-based names also make style variations, alternative colour schemes, much easier.

Typography

Define font families, sizes and options:

Fluid typography

With fluid set to true, WordPress generates font sizes that scale smoothly between a minimum and maximum, based on the screen width, using CSS clamp. This removes the need for many media queries. You can set explicit minimum and maximum values for each size, as shown above, or let WordPress calculate them.

Font families

Font families are defined in settings too, with fontFace declarations that point to font files in your theme. WordPress generates the @font-face rules automatically. Self-hosting fonts this way is good for performance and privacy.

Spacing

Spacing settings control padding, margins and gaps between blocks. A spacing scale gives editors consistent options:

Editors then choose from your scale, rather than typing arbitrary pixel values. Consistent spacing is one of the biggest differences between a site that looks designed and one that looks assembled.

Layout

Layout settings define content widths:

contentSize is the default width for content, like paragraphs. wideSize is used when blocks are set to wide alignment. Full-width alignment spans the whole viewport. These settings keep reading widths comfortable while allowing wider sections when needed.

Global styles

The styles section applies your design tokens to the site. Use the CSS variables generated from your settings:

The elements key styles common HTML elements, like links, headings and buttons, consistently across all blocks. Pseudo-classes like hover and focus are supported for links and buttons.

Block-level settings and styles

You can override settings and styles for specific blocks. For example, allow custom colours only in the cover block, or give the quote block a distinct style:

Block-level settings are powerful for fine-tuning the editing experience: enabling features only where they make sense and keeping the rest of the interface clean.

Locking down the editor

One of theme.json’s biggest benefits for client sites is controlling what editors can change. You can disable:

  • Custom colours, gradients and duotone.
  • Custom font sizes and line heights.
  • Border controls.
  • Typography options like letter spacing and text decoration.
  • Specific features for specific blocks.

The goal isn’t to restrict editors unnecessarily. It’s to give them a curated set of options that always look good together. Editors usually appreciate this, because they can’t accidentally break the design.

Style variations

Block themes can include style variations: alternative JSON files in a styles folder that change colours, typography or other settings. Site owners can switch between them in the Site Editor. WordPress 6.6 also introduced colour and typography presets as partial variations.

Variations are useful for offering light and dark schemes, seasonal campaigns or different brand presentations without separate themes.

Custom properties

The custom key lets you define your own CSS variables alongside the presets:

These become variables like –wp–custom–radius–small, available in your CSS. WordPress also supports shadow presets directly in theme.json in recent versions.

theme.json and the Site Editor

When users change global styles in the Site Editor, their changes are saved in the database, layered on top of the theme’s theme.json. This means:

  • Theme updates don’t overwrite user customisations.
  • But customisations live in two places: the file and the database.

On client projects, I keep theme.json as the source of truth and avoid making design changes in the Site Editor on production, except for content. If changes are made in the editor, they can be exported back into the theme files.

Child themes and theme.json

A child theme’s theme.json is merged with the parent’s. You only need to include the values you want to change. This makes it easy to customise a block theme’s colours or typography without touching templates.

Common mistakes

  • Too many options. Twelve colours and ten font sizes overwhelm editors and create inconsistency.
  • Leaving custom colours enabled on brand-sensitive sites.
  • Hard-coding values in CSS instead of using theme.json variables, which breaks consistency when tokens change.
  • Forgetting editor parity. Styles added only in front-end CSS won’t appear in the editor; theme.json styles appear in both.
  • Not using the schema, which leads to typos that silently fail.

A workflow for building theme.json

When I start a new client theme, I build theme.json in a particular order. First, the colour palette, agreed with the client or taken from their brand guidelines, with role-based names. Second, the typography: font families, a type scale of four to six sizes and line heights. Third, the spacing scale. Fourth, layout widths. Only then do I move on to global styles and block-level styles.

This order matters because each layer builds on the previous one. Global styles reference colour, font and spacing variables, and block styles reference global ones. If you start styling blocks before your tokens are settled, you’ll end up with hard-coded values scattered everywhere.

Once the tokens are in place, I build a test page containing every core block the client is likely to use: headings, paragraphs, lists, quotes, buttons, images, galleries, columns, groups, covers and tables. I check it on the front end and in the editor, in light and dark contexts, and on a phone. This page becomes my reference whenever I change theme.json later, so I can see at a glance whether something has broken.

Debugging theme.json

When a setting doesn’t seem to work, start by checking that the JSON is valid; a single missing comma breaks the whole file silently. Then check which version of theme.json your file declares, because some properties only exist in newer versions. Look at the generated CSS in your browser’s developer tools to see whether WordPress created the variables you expected. And remember that global styles saved in the Site Editor override the file, so check whether a user customisation is masking your change.

Frequently asked questions

Can I use theme.json in a classic theme?

Yes. Classic themes can include theme.json to control editor settings like colour palettes and font sizes, and some styles. It’s a good way to improve the block editor experience in older themes.

Does theme.json replace style.css?

Not entirely. theme.json handles design tokens and many styles, but you may still need CSS for complex layouts, animations or details theme.json doesn’t support. Keep custom CSS minimal and use theme.json variables within it.

Why aren’t my theme.json changes showing?

Check for JSON syntax errors, caching and whether the Site Editor has saved user styles that override the file. User customisations in the database take priority over theme.json.

How do I let editors use custom colours on just one block?

Disable custom colours globally in settings, then enable them for that block under settings.blocks.

Final thoughts

theme.json turns your design system into configuration that WordPress understands, applying it consistently in the editor and on the front end. Define a focused palette, a sensible type scale and a spacing system, apply them through global and block-level styles, and lock down options that would let editors drift from the design. Done well, it gives you a site that’s easy to edit and very hard to make look bad.