You want your WordPress site to use your brand colours and fonts everywhere: in buttons, headings, links and inside the block editor while you write. In a block theme, the place to set all of that once is a single file called theme.json. Get it right and every block, every pattern and the editor itself follow the same design rules, with no extra CSS to fight.
This guide shows how to customise colours and fonts with theme.json safely: where the file lives, how the colour palette and font settings work, how to style elements and individual blocks, and how to avoid the mistakes that make changes “not show up”.
What theme.json does

theme.json is a configuration file in the root of a block theme (and optionally in classic themes). It has two main jobs:
settingsdecide which design options exist: the colour palette, font families, font sizes, spacing scale, and whether users may pick custom colours.stylesapply those options: the default background and text colour, link colours, heading fonts, button styles and per-block styles.
WordPress reads the file, combines it with WordPress’s own defaults and with any changes made in the Site Editor, and outputs the result as CSS. Every preset becomes a CSS custom property, for example a palette colour with the slug primary becomes --wp--preset--color--primary. Those variables are available on the front end and in the editor, which is why the editor preview matches the live site.
The current file format is version 3, which is what the latest WordPress releases (WordPress 7.1 at the time of writing) still use. Start every file with the version and a $schema line so your code editor can autocomplete and validate keys:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {},
"styles": {}
}
Before you edit: use a child theme
Never edit the theme.json of a theme you downloaded. The next theme update will replace your file. Create a child theme instead: a folder in wp-content/themes/ with a style.css whose header names the parent, plus your own theme.json.
/*
Theme Name: My Brand Child
Template: twentytwentyfive
*/
The Template line must match the parent theme’s folder name exactly. A child theme’s theme.json is merged on top of the parent’s, so you only write the parts you want to change; everything else is inherited. Test on a copy of the site first if you can; our list of WordPress staging plugins covers easy ways to create one.
Step 1: define your colour palette
Add your brand colours under settings.color.palette. Each entry needs a slug (used in CSS variable names and class names), a color and a human-readable name shown in the editor.
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"color": {
"palette": [
{ "slug": "base", "color": "#ffffff", "name": "Base" },
{ "slug": "contrast", "color": "#111827", "name": "Contrast" },
{ "slug": "primary", "color": "#1863dc", "name": "Primary" },
{ "slug": "accent", "color": "#ffc600", "name": "Accent" }
],
"defaultPalette": false,
"custom": false
}
}
}
Two optional switches matter for a brand site:
"defaultPalette": falsehides WordPress’s default colours from the colour picker, so editors only see your palette."custom": falseremoves the free colour picker, so nobody can pick an off-brand colour for a single block.
Keep slugs short and descriptive of role rather than hue (primary, not blue). If you rebrand later, you change the hex value once and the slug, CSS variables and every block that uses it stay valid.
Step 2: add your fonts

Fonts go under settings.typography.fontFamilies. For a web font you host yourself, put the .woff2 files in the theme (for example assets/fonts/) and describe them with fontFace. Paths use the file:./ prefix, relative to theme.json.
"typography": {
"fontFamilies": [
{
"name": "Inter",
"slug": "inter",
"fontFamily": "Inter, sans-serif",
"fontFace": [
{
"fontFamily": "Inter",
"fontWeight": "300 800",
"fontStyle": "normal",
"src": [ "file:./assets/fonts/inter/Inter-VariableFont.woff2" ]
}
]
},
{
"name": "System Serif",
"slug": "system-serif",
"fontFamily": "Georgia, 'Times New Roman', serif"
}
],
"fluid": true,
"fontSizes": [
{ "slug": "small", "size": "0.9rem", "name": "Small" },
{ "slug": "medium", "size": "1.1rem", "name": "Medium" },
{ "slug": "large", "size": "1.75rem", "name": "Large",
"fluid": { "min": "1.4rem", "max": "1.75rem" } }
]
}
This block goes inside settings, next to color. A variable font file such as the one above covers a whole weight range in one download, which is lighter than loading four separate weights. "fluid": true lets font sizes scale smoothly between small and large screens, and an individual size can set its own min and max.
Hosting fonts locally like this avoids sending visitors’ IP addresses to a third-party font service and usually loads faster than an external stylesheet. It also helps with the text flash problem many sites have, because WordPress loads the font files declared here for you.
Each family becomes --wp--preset--font-family--{slug} and each size --wp--preset--font-size--{slug}.
Step 3: apply colours and fonts with styles
Settings only make options available. To actually use them, set them in styles. Refer to presets with the var:preset|type|slug shorthand or with the full CSS variable:
"styles": {
"color": {
"background": "var:preset|color|base",
"text": "var:preset|color|contrast"
},
"typography": {
"fontFamily": "var:preset|font-family|inter",
"fontSize": "var:preset|font-size|medium",
"lineHeight": "1.6"
},
"elements": {
"link": {
"color": { "text": "var:preset|color|primary" },
":hover": { "color": { "text": "var:preset|color|contrast" } }
},
"heading": {
"typography": { "fontWeight": "700", "lineHeight": "1.2" }
},
"button": {
"color": {
"background": "var:preset|color|primary",
"text": "var:preset|color|base"
},
":hover": {
"color": { "background": "var:preset|color|contrast" }
}
}
}
}
Top-level color and typography set the site defaults. elements targets HTML elements that appear in many blocks: links, headings (or h1 to h6 individually), buttons, captions and more. Pseudo-classes such as :hover and :focus are supported for links and buttons.
Step 4: style individual blocks

When one block type needs its own look, use styles.blocks with the block’s name:
"blocks": {
"core/quote": {
"border": { "left": { "color": "var:preset|color|accent", "width": "4px", "style": "solid" } },
"typography": { "fontStyle": "italic" }
},
"core/navigation": {
"typography": { "fontSize": "var:preset|font-size|small", "fontWeight": "600" }
}
}
This keeps block-specific styling in one place instead of scattered custom CSS. Because the values point to presets, a palette change updates these blocks too.
theme.json vs the Site Editor vs custom CSS

You can change many of the same things in Appearance → Editor → Styles without touching code. The order of priority matters: WordPress defaults are overridden by the theme’s theme.json, which is overridden by your child theme’s theme.json, and changes saved in the Site Editor’s Styles panel override all of those. Site Editor changes are stored in the database, not in your files.
A practical split for most sites:
- theme.json for the design system: palette, fonts, sizes, global element styles. It lives in version control and moves with the theme.
- Site Editor Styles for quick visual adjustments by non-developers.
- Custom CSS only for things
theme.jsoncannot express, such as complex selectors or animations.
Common mistakes and fixes
My changes do not appear
- Site Editor overrides. If someone changed the same setting in Appearance → Editor → Styles, that saved value wins. Open Styles, use the menu next to the Styles title to reset to the theme defaults, and reload.
- Wrong theme active. Make sure the child theme is the active theme, not the parent.
- Caching. Clear any page cache or performance plugin cache, and your CDN, then hard-reload the browser.
The whole site lost its styles
The file is probably invalid JSON: a trailing comma, a missing quote or a missing bracket. WordPress cannot read a broken file. Paste it into a JSON validator or open it in an editor that uses the $schema line, fix the error and reload. If you see PHP warnings, our guide to reading PHP error logs helps you find them.
The font does not load
Check the src path: it must start with file:./ and be relative to theme.json, and the file name is case-sensitive on most Linux servers. Also make sure the fontFamily name inside fontFace matches the name used in the family’s fontFamily stack.
Editors can still pick random colours
Set "custom": false and "defaultPalette": false under settings.color. You can also turn off gradients with "customGradient": false and "defaultGradients": false.
I edited the file in the dashboard and want to stop that
Editing theme files through the dashboard is risky on a live site. Our guide on how to disable the theme and plugin editor shows how to switch it off, so changes go through files and version control instead.
Starting from a finished design
If you would rather start from a complete layout and only adjust the colours and type to match your brand, a ready-made theme saves days of work. ET Poren suits portfolios and creative studios, and ET Epic Onepage is a single-page layout for small businesses. Check each theme’s documentation for where its colour and font settings live before you customise. You can browse more in our free WordPress themes collection.
Wrapping up
Create a child theme, define your palette and fonts under settings, apply them under styles for the whole site, elements and individual blocks, and keep quick tweaks in the Site Editor. With presets in place, a future rebrand is a few hex values and a font file away. If you are adding a dark colour scheme next, our guide on adding dark mode to WordPress is a good follow-up. For every available key, see the official Global Settings and Styles documentation and the theme.json reference.
- WordPress theme.json: Customize Colors and Fonts the Right Way - October 7, 2026
- 7 Best Website Speed Test Tools to Diagnose Performance Issues - August 31, 2026
- Scheduled Tasks Explained: Why WordPress and Joomla Cron Jobs Don’t Always Run on Time - August 30, 2026







