Customize a Block Theme: theme.json or Child Theme?

Customize a Block Theme: theme.json or Child Theme?

To customize a block theme in WordPress 7.1 you have four places to put the change, and only one of them is the right answer for any given change. The Site Editor’s Styles panel is the fastest, and it is also the one that quietly overrides your theme’s theme.json forever afterwards — which is why so many developers edit theme.json, reload the front end, and see absolutely nothing happen.

That is not a caching problem and it is not a bug. It is the global styles cascade doing exactly what it is documented to do. Once you can see the four layers, picking where a change belongs stops being a matter of taste and becomes a two-second decision.

Who this is for

Anyone running a block theme — Twenty Twenty-Five, Ollie, Bloqra, Twenty Twenty-Four — who wants a design change to stick through a theme update. If you are on a classic theme with a style.css and PHP template files, the child theme advice here still applies but the theme.json and Styles sections do not. Check which you have at Appearance: if you see Editor instead of Customize, you are on a block theme.

The four layers, and why your change disappeared

WordPress builds the stylesheet for a block theme by merging several sources of the same theme.json data structure. The Theme Handbook lists them in order: WordPress defaults, parent theme, child theme, user customizations — and it is explicit that user customizations “can override theme.json, templates, and patterns”.

LayerWhere it livesSurvives a theme update?Who can change it
1. WordPress defaultsCore’s own theme.json in wp-includes/Replaced on every core updateNobody — don’t touch it
2. Parent themewp-content/themes/your-theme/theme.jsonNo. Overwritten by the theme’s next releaseYou, if you own the theme
3. Child themewp-content/themes/your-theme-child/theme.jsonYesYou
4. User customizationsA wp_global_styles post in the database, written by the Site EditorYes — and it keeps winning over layers 1–3Any editor with access to the Site Editor
Layer order per the WordPress Theme Handbook’s child themes page. Each later layer merges over the earlier ones rather than replacing them wholesale.

Here is the sequence that catches people. Someone opens Appearance → Editor → Styles, nudges the body text colour, and hits Save. WordPress writes a wp_global_styles record for that theme containing that one property. Three weeks later you decide the body colour should be different, edit styles.color.text in theme.json, deploy it, and nothing changes — because layer 4 still has an opinion about that exact property and layer 4 wins.

It is property-level, not file-level, which is what makes it so confusing to debug. Your other theme.json changes apply fine. Only the handful of properties someone has ever touched in the Styles panel are frozen, and nothing in the interface tells you which ones.

Where to customize a block theme: four places, compared

Those four layers map onto four practical ways to make a change. On a narrow screen this table stacks into one card per option.

ApproachNeeds file access?Version controllableOverridden byBest forWhere it hurts
Site Editor StylesNoNo — it is a database rowNothing. It is the top layerOne-off colour and typography decisions on a site you alone manageInvisible to your repo, blocks future theme.json edits, and no per-property reset
Child theme theme.jsonYesYesSite Editor StylesAnything you want in Git, reviewed, and deployed — the default for client workA second theme folder to maintain, and the child gets its own separate Styles record
Style variation in /stylesYesYesSite Editor StylesOffering two or three complete looks someone can switch betweenOnly usable if you control the theme folder; a parent-theme update wipes a variation you added to the parent
Additional CSSNoNoNothing — it loads lastThe 5% a design system genuinely cannot express: a third-party widget, a print ruleUnbounded. It is where block themes go to become classic themes again
“Overridden by” is the column that decides most arguments. Anything in a file loses to anything saved in the Site Editor.

The criteria I judged these on

Stating these up front so you can disagree with the weighting rather than the conclusion. These are the things that have actually gone wrong on sites I have had to fix.

Does it survive a theme update? This is the only criterion that is non-negotiable. A change written into a parent theme’s files is a change you will lose, on a date you do not control, probably without noticing.
Can you see it in a diff? A design change you cannot review, revert, or deploy is a design change nobody on the team knows about. Database-stored styles fail this completely — they move between environments only in a database dump.
Does it use the theme’s own design tokens? A value set through presets stays consistent when the palette changes. A raw hex code pasted into Additional CSS does not, and in two years nobody will remember why that one button is a slightly different purple.
Who needs to be able to change it later? If the answer is “a non-technical editor, next week”, a file-based approach is the wrong tool no matter how clean it is. Be honest about this before you build something only you can edit.
How hard is it to undo? Deleting a line from theme.json is trivial. Undoing a Styles panel change is “Reset to defaults”, which clears everything anyone has ever saved there — there is no per-property undo weeks later.

Option 1: the Site Editor Styles panel

Go to Appearance → Editor, then click the half-shaded circle icon at the top right. You get panels for Typography, Colors, Layout, Background and Blocks. In WordPress 7.1 these also carry the new responsive controls, so you can set a different value for mobile and tablet without a media query — covered separately in how to use responsive styles in WordPress 7.1 without custom CSS.

This is genuinely the right choice in two situations: you are the only person who will ever touch the site’s design, or you are prototyping and want to see the change before you commit to it. It is fast, it is live, and it requires no deployment.

Two things to know before you save here

First, the record is stored per theme. Activate a child theme later and your parent-theme Styles customizations do not come with you — WordPress looks up the global styles post for the theme that is currently active. Second, the only documented way back is the three-dot menu in the Styles panel → Reset to defaults, which is all-or-nothing. Prototype here by all means; just know that “I’ll tidy it into theme.json later” is a job, not a formality.

Option 2: a child theme with its own theme.json

This is the answer for anything you want in version control, and for every site you do not personally own. A block child theme is smaller than a classic one — frequently just two files, because the Theme Handbook notes the style.css enqueue step is “often not needed for block themes because their style handling is generally done via theme.json“.

WP-CLI will scaffold the folder, though be aware it is written for classic themes and will give you a functions.php with a parent-stylesheet enqueue you probably want to delete:

wp scaffold child-theme bloqra-child --parent_theme=bloqra \
  --theme_name="Bloqra Child" --activate

Or write the two files by hand. The Template header is the part that matters — it must be the parent’s directory name, not its display name:

/* wp-content/themes/bloqra-child/style.css */
/*
Theme Name: Bloqra Child
Template: bloqra
Version: 1.0.0
*/

Then the file that does the work. theme.json is on schema version 3, which has been current since WordPress 6.6, so put "version": 3 at the top and nothing else. Below is a child theme.json that overrides three things against the real preset slugs from the theme this site runs:

{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "settings": {
    "color": {
      "palette": [
        { "slug": "primary", "name": "Primary", "color": "#1F6FEB" },
        { "slug": "accent",  "name": "Accent",  "color": "#00D5BE" }
      ]
    }
  },
  "styles": {
    "typography": {
      "fontFamily": "var:preset|font-family|serif",
      "lineHeight": "1.7"
    },
    "blocks": {
      "core/button": {
        "border": { "radius": "4px" }
      }
    }
  }
}

Two details in there are worth pulling out. Redeclaring a palette entry with the same slug as the parent’s replaces that colour everywhere it is used — every block already set to “Primary” follows along, which is the whole point of working in tokens rather than hex codes. And inside styles you reference presets with the var:preset|... syntax, not CSS var(); WordPress compiles it to var(--wp--preset--font-family--serif) for you.

To find the slugs your own theme actually defines, read its theme.json rather than guessing. The theme I run here exposes a nine-step primary ramp, a neutral ramp, seven spacing steps from 2-xs to 2-xl, and three font families — sans, serif, mono. Disclosure: I build and maintain Bloqra, the theme and blocks used on this site. Twenty Twenty-Five, bundled free with WordPress, is organised the same way and is a perfectly good place to learn this on — the mechanics in this article are core behaviour, not anything specific to one theme.

Templates work the same way: drop templates/single.html into the child and it replaces the parent’s. The Handbook is explicit that you can “overwrite any template, part, or pattern that exists in the parent theme”, with the caveat that a replacement pattern must keep the same registered Slug field.

Option 3: a style variation in /styles

A style variation is a JSON file in the theme’s /styles folder that offers a complete alternative look, selectable from the Styles panel. Each file needs a unique filename and sits at the theme root — your-theme/styles/latte.json in the Handbook’s example. The contents are a theme.json fragment plus a title:

/* wp-content/themes/bloqra-child/styles/high-contrast.json */
{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "title": "High Contrast",
  "settings": {
    "color": {
      "palette": [
        { "slug": "primary", "name": "Primary", "color": "#000000" }
      ]
    }
  },
  "styles": {
    "color": { "background": "#FFFFFF", "text": "#111111" }
  }
}

Use this when the real requirement is “give the client two looks to choose from”, not “change this one colour”. It is the only option in this article that hands a non-technical user a meaningful design switch without giving them the ability to break the type scale. Put it in the child theme, not the parent, for the same reason as everything else here.

Option 4: Additional CSS, and when it is defensible

Block themes did not abolish CSS. There are two places for it. Site-wide CSS lives behind the ellipsis menu in the header of the Styles panel — the location moved during the 6.x series, so if a tutorial tells you it is under the Customizer, that tutorial predates your WordPress. Per-block CSS is better: open Styles → Blocks, pick a block, and use the Additional block CSS field. You write declarations only, with no selector and no braces:

font-style: italic;
border-left-width: 4px;

WordPress scopes that to the block for you — entered on the Quote block it comes out as .wp-block-quote { font-style: italic; }. That scoping is why per-block CSS is almost always preferable to a site-wide rule: you cannot accidentally hit something three templates away.

Defensible uses: styling markup a third-party plugin outputs that no block controls reach; a @media print rule; a one-off fix with a ticket number in a comment above it. Not defensible: rebuilding your type scale, because every rule you add there is a rule the Styles panel no longer knows about and your next designer cannot find.

Diagnosing a theme.json edit that does nothing

Work through these in order. The first two cost thirty seconds and account for most cases.

  1. Check the JSON is valid. One trailing comma and WordPress silently ignores the entire file rather than erroring. php -r 'var_dump(json_decode(file_get_contents("theme.json")));' returning NULL is your answer.
  2. Check you are editing the active theme. With a child theme active, the parent’s theme.json is still read, but any property the child also declares wins. Editing the parent’s copy of a property the child overrides does nothing, correctly.
  3. Check whether a user global styles record exists. wp_global_styles is a private post type, so it never appears in the admin. From the shell:
# One row per theme that has ever been customized in the Site Editor
wp post list --post_type=wp_global_styles --post_status=publish \
  --fields=ID,post_title,post_name,post_modified

A row whose post_name matches your active theme’s directory means layer 4 is in play. If that is the culprit, the supported fix is the Styles panel’s three-dot menu → Reset to defaults, after you have written down what was in there, because it clears the lot. Do that on staging first and test the site the way you would after a major update before repeating it on production.

Two more causes worth knowing. A full-page cache or a CDN will happily serve the old compiled stylesheet — clear both before you start doubting the cascade. And if the change still will not land after all of that, turn on logging and look for a JSON or theme error rather than guessing; enabling WordPress debugging and reading the error log safely walks through doing that without exposing notices to visitors.

So which one should you use?

  • Your own blog, you are the only editor: Site Editor Styles. Genuinely. The version-control argument does not apply to a site with one person on it, and you will make the change in ten seconds.
  • A client site, or anything with a staging environment: child theme with its own theme.json, and tell the client not to use the Styles panel. Then actually check — a single saved property there will undo your next deployment in a way that looks like a caching bug.
  • You own the theme: change the parent’s theme.json and ship a new version. The whole child-theme discussion exists because you cannot update someone else’s files; it does not apply to your own.
  • Two or three complete looks to choose between: style variations in the child theme’s /styles folder.
  • No file access at all — managed host, no SFTP, no CLI: Styles panel plus per-block Additional CSS is your entire toolkit. Export a copy of what you set somewhere outside WordPress, because “Reset to defaults” is the only undo and it is total.
  • A plugin’s markup you cannot reach with block controls: Additional block CSS, scoped as tightly as you can get it, with a comment saying why.

One habit worth adopting whichever you pick: write down where your design lives. “Colours and type in bloqra-child/theme.json; nothing in the Styles panel” is one line in a README and it saves the next person — frequently you, in eight months — an afternoon of confusion. If you are still choosing a theme, the ability to keep that line short is a reasonable thing to weigh; this checklist for choosing a WordPress theme covers the rest.

Frequently asked questions

Do I still need a child theme for a block theme?

Only if you need your changes in files rather than in the database. The Site Editor can override anything theme.json sets, so a child theme is no longer the only way to customize safely — it is the way to customize reviewably. For client work, team work, or anything with a deployment pipeline, that distinction is the whole argument. For a personal blog it often is not.

Why doesn’t my theme.json change show on the front end?

Most often because someone saved that same property in the Site Editor’s Styles panel, and user customizations sit above the theme in the cascade. Check for a wp_global_styles post for your active theme, clear any page cache, and confirm the JSON parses. A single syntax error makes WordPress ignore the whole file without complaint.

Does a block child theme need a functions.php?

Usually not. A style.css with a Template header pointing at the parent’s folder name, plus a theme.json, is a working block child theme. The Theme Handbook notes the parent-stylesheet enqueue is “often not needed for block themes”. Add a functions.php when you have PHP to run — it loads alongside the parent’s rather than replacing it.

Will activating a child theme keep my Site Editor customizations?

No. Global styles are stored per theme — WordPress looks up the global styles post for whichever theme is active — so switching to a child theme starts you from its theme.json with no user layer. Screenshot or note your Styles settings before you switch, then reproduce the ones you want in the child’s theme.json, which is where they should have been anyway.

What theme.json version should I use in 2026?

Version 3, declared as "version": 3. It has been the current schema since WordPress 6.6 and remains so on WordPress 7.1. Copying "version": 2 from an older tutorial still works but puts you on a schema with different defaults for things like defaultFontSizes, which produces exactly the sort of inherited-value confusion this article is about.

Can I stop clients using the Styles panel?

Access to the Site Editor follows the edit_theme_options capability, so an Editor-role user does not get it while an Administrator does. If the people writing content do not need it, giving them the Editor role rather than Administrator removes the problem without any custom code — and is worth doing for several other reasons too.

Is Additional CSS bad practice in a block theme?

In small, scoped doses, no — some things genuinely have no block control, and pretending otherwise produces worse workarounds. Prefer the per-block Additional block CSS field over site-wide CSS so WordPress scopes the rule for you. The failure mode is volume: once there are two hundred lines in there, your design has quietly moved out of the design system and back into a stylesheet nobody maintains.

Sources

Leave a Reply to This Post