Type something to search...
How to use theme.json in WordPress?

How to use theme.json in WordPress?

To use theme.json in WordPress, create a file named theme.json in the root folder of your theme (or child theme) and add a JSON object with a version number, a settings section, and a styles section. Settings define your design options, such as color palettes, font sizes, and layout widths, and control which tools appear in the editor. Styles apply those options to your site, elements, and individual blocks. WordPress reads the file automatically and generates the matching CSS.

theme.json is the heart of modern WordPress theming. It powers Global Styles in the Site Editor, keeps the editor and front end visually in sync, and replaces a large amount of hand-written CSS and add_theme_support() calls. This guide explains how the file is structured, walks through each major section with working examples, and covers style variations, child themes, and a few PHP techniques for working with theme.json data.

What Is theme.json?

theme.json is a configuration file that tells WordPress about a theme's design system. It was introduced in WordPress 5.8 and has grown with each release. It does three main jobs:

  1. Defines presets: Colors, gradients, font families, font sizes, spacing sizes, and shadows that users can pick from in the editor.
  2. Controls editor features: Enables or disables design tools, such as custom colors, border controls, or line height, globally or per block.
  3. Applies styles: Sets default styles for the whole site, for HTML elements like links and headings, and for specific blocks.

Block themes rely on theme.json heavily, but classic themes can use it too. Adding a theme.json file to a classic theme gives you centralized control over the block editor's options.

The Basic Structure

Here's the skeleton of a theme.json file:

{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "settings": {},
  "styles": {},
  "customTemplates": [],
  "templateParts": [],
  "patterns": []
}
  • $schema: Optional, but highly recommended. Code editors like VS Code use it to offer autocomplete and validation.
  • version: The file format version. Version 3 is current and requires WordPress 6.6 or later. Use version 2 if you need to support older releases.
  • settings: Presets and feature controls.
  • styles: The actual styles applied to the site.
  • customTemplates: Custom page templates (block themes).
  • templateParts: Metadata for template parts (block themes).
  • patterns: Slugs of patterns from the WordPress.org Pattern Directory to register.

Working With Settings

The settings section is where you define what's available to users.

Enabling Design Tools

The simplest and most useful setting is appearanceTools. Setting it to true enables a group of tools at once, including border, link color, spacing (margin, padding, block gap), typography line height, and background image controls:

"settings": {
"appearanceTools": true
}

You can then turn individual features off if needed, which gives you a lot of control with very little code.

Color Settings

Define your brand palette and choose which color options users can access:

"settings": {
"color": {
"defaultPalette": false,
"defaultGradients": false,
"custom": false,
"customGradient": false,
"palette": [
{ "slug": "base", "color": "#ffffff", "name": "Base" },
{ "slug": "contrast", "color": "#0f172a", "name": "Contrast" },
{ "slug": "primary", "color": "#2563eb", "name": "Primary" },
{ "slug": "secondary", "color": "#f59e0b", "name": "Secondary" },
{ "slug": "muted", "color": "#f1f5f9", "name": "Muted" }
],
"gradients": [
{
"slug": "primary-to-secondary",
"gradient": "linear-gradient(135deg, #2563eb 0%, #f59e0b 100%)",
"name": "Primary to Secondary"
}
]
}
}

Setting custom to false removes the free color picker, so users can only choose from your palette. This is great for keeping client sites on brand.

Typography Settings

Define font families, sizes, and which typography controls are available:

"settings": {
"typography": {
"fluid": true,
"defaultFontSizes": false,
"fontFamilies": [
{
"slug": "body",
"name": "Inter",
"fontFamily": "Inter, system-ui, sans-serif",
"fontFace": [
{
"fontFamily": "Inter",
"fontStyle": "normal",
"fontWeight": "100 900",
"fontDisplay": "swap",
"src": [ "file:./assets/fonts/inter-variable.woff2" ]
}
]
},
{
"slug": "mono",
"name": "Monospace",
"fontFamily": "ui-monospace, SFMono-Regular, Menlo, monospace"
}
],
"fontSizes": [
{ "slug": "small", "size": "0.875rem", "name": "Small" },
{ "slug": "medium", "size": "1.125rem", "name": "Medium" },
{ "slug": "large", "size": "1.5rem", "name": "Large", "fluid": { "min": "1.25rem", "max": "1.5rem" } },
{ "slug": "x-large", "size": "2.25rem", "name": "Extra Large", "fluid": { "min": "1.75rem", "max": "2.25rem" } }
]
}
}

A few things to note:

  • fontFace lets WordPress load self-hosted fonts from your theme. The file:./ prefix points to a path relative to the theme folder. WordPress generates the @font-face rules for you, on both the front end and in the editor.
  • fluid enables fluid typography, where font sizes scale smoothly between a minimum and maximum based on the viewport width.
  • defaultFontSizes: false hides WordPress's built-in font size presets so only yours appear.

Self-hosting fonts is generally better for performance and privacy than loading them from an external service.

Spacing Settings

Control the spacing scale used for padding, margin, and block gap:

"settings": {
"spacing": {
"units": [ "px", "rem", "%", "vw", "vh" ],
"defaultSpacingSizes": false,
"spacingSizes": [
{ "slug": "20", "size": "0.5rem", "name": "XS" },
{ "slug": "30", "size": "1rem", "name": "S" },
{ "slug": "40", "size": "1.5rem", "name": "M" },
{ "slug": "50", "size": "clamp(2rem, 4vw, 3rem)", "name": "L" },
{ "slug": "60", "size": "clamp(3rem, 6vw, 5rem)", "name": "XL" }
]
}
}

Using numeric slugs like 20, 30, and 40 matches WordPress's default naming, which helps patterns from other themes work with your scale.

Layout Settings

Define your content and wide widths:

"settings": {
"layout": {
"contentSize": "720px",
"wideSize": "1240px"
},
"useRootPaddingAwareAlignments": true
}
  • contentSize is the default width of content in constrained layouts, such as post text.
  • wideSize is the width used when a block is set to "wide" alignment.
  • useRootPaddingAwareAlignments makes full-width blocks extend to the edges while keeping content inside them aligned with the site's padding.

Shadow and Border Settings

You can also provide shadow presets:

"settings": {
"shadow": {
"defaultPresets": false,
"presets": [
{ "slug": "soft", "name": "Soft", "shadow": "0 4px 20px rgba(15, 23, 42, 0.08)" },
{ "slug": "strong", "name": "Strong", "shadow": "0 10px 40px rgba(15, 23, 42, 0.18)" }
]
}
}

Per-Block Settings

Settings can be overridden for specific blocks under settings.blocks. For example, you might allow a custom color picker only on the Button block, or give the Heading block its own palette:

"settings": {
"blocks": {
"core/button": {
"color": {
"custom": true
}
},
"core/paragraph": {
"typography": {
"dropCap": false
}
}
}
}

How Presets Become CSS Variables

Every preset you define becomes a CSS custom property that WordPress outputs on the page. The naming pattern is --wp--preset--{type}--{slug}:

  • --wp--preset--color--primary
  • --wp--preset--font-size--large
  • --wp--preset--font-family--body
  • --wp--preset--spacing--40
  • --wp--preset--shadow--soft

WordPress also generates utility classes, such as .has-primary-color and .has-primary-background-color, which the editor adds to blocks when a user picks a color.

You can use these variables anywhere in your CSS:

.site-notice {
  background: var(--wp--preset--color--muted);
  color: var(--wp--preset--color--contrast);
  padding: var(--wp--preset--spacing--30);
  box-shadow: var(--wp--preset--shadow--soft);
}

You can also define your own variables with settings.custom. For example, "custom": { "line-height": { "tight": "1.2" } } becomes --wp--custom--line-height--tight.

Working With Styles

The styles section applies your presets. You can reference presets using either full CSS variables or the shorthand var:preset|type|slug syntax.

Root Styles

Styles at the top level of styles apply to the whole site (the body):

"styles": {
"color": {
"background": "var:preset|color|base",
"text": "var:preset|color|contrast"
},
"typography": {
"fontFamily": "var:preset|font-family|body",
"fontSize": "var:preset|font-size|medium",
"lineHeight": "1.65"
},
"spacing": {
"blockGap": "1.5rem",
"padding": {
"top": "0",
"bottom": "0",
"left": "var:preset|spacing|40",
"right": "var:preset|spacing|40"
}
}
}

Element Styles

Elements let you style common HTML elements across all blocks. Supported elements include link, heading, h1 through h6, button, caption, and cite:

"styles": {
"elements": {
"link": {
"color": { "text": "var:preset|color|primary" },
":hover": {
"color": { "text": "var:preset|color|contrast" }
},
":focus": {
"outline": { "width": "2px", "style": "solid", "color": "var:preset|color|primary" }
}
},
"heading": {
"typography": { "fontWeight": "700", "lineHeight": "1.2" }
},
"h1": {
"typography": { "fontSize": "var:preset|font-size|x-large" }
},
"button": {
"color": {
"background": "var:preset|color|primary",
"text": "var:preset|color|base"
},
"border": { "radius": "8px" },
":hover": {
"color": { "background": "var:preset|color|contrast" }
}
}
}
}

Pseudo-selectors such as :hover, :focus, :active, and :visited are supported for links and buttons.

Block Styles

Target individual blocks under styles.blocks:

"styles": {
"blocks": {
"core/quote": {
"border": {
"left": { "color": "var:preset|color|primary", "width": "4px", "style": "solid" }
},
"spacing": {
"padding": { "left": "var:preset|spacing|40" }
},
"typography": { "fontStyle": "italic" }
},
"core/site-title": {
"typography": { "fontWeight": "800" },
"elements": {
"link": {
"typography": { "textDecoration": "none" }
}
}
}
}
}

Blocks can contain nested elements, as shown with the Site Title's link. You can also add a css property for anything the schema doesn't cover.

Templates, Template Parts, and Patterns

In block themes, theme.json also registers metadata for templates and parts:

"customTemplates": [
{ "name": "page-landing", "title": "Landing Page", "postTypes": [ "page" ] }
],
"templateParts": [
{ "name": "header", "title": "Header", "area": "header" },
{ "name": "footer", "title": "Footer", "area": "footer" }
],
"patterns": [ "short-text-surrounded-by-round-images" ]

The name values must match files in the templates and parts folders, such as templates/page-landing.html. The patterns array accepts slugs from the WordPress.org Pattern Directory.

Creating Style Variations

Style variations let users switch between different looks for your theme. Create a styles folder in your theme and add JSON files that override parts of theme.json:

{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "title": "Midnight",
  "settings": {
    "color": {
      "palette": [
        { "slug": "base", "color": "#0f172a", "name": "Base" },
        { "slug": "contrast", "color": "#f8fafc", "name": "Contrast" },
        { "slug": "primary", "color": "#60a5fa", "name": "Primary" },
        { "slug": "secondary", "color": "#fbbf24", "name": "Secondary" },
        { "slug": "muted", "color": "#1e293b", "name": "Muted" }
      ]
    }
  }
}

Save it as styles/midnight.json. It will now appear under Appearance > Editor > Styles. Because your styles reference presets by slug, swapping the palette automatically restyles the whole site.

Recent versions of WordPress also support partial variations, such as color-only or typography-only files. Add "blockTypes" to a variation file to create a block style variation that applies to specific blocks, which then appears as a style option in the block toolbar.

How the Style Hierarchy Works

WordPress merges theme.json data from several sources, with each layer overriding the one before it:

  1. Core defaults: WordPress's own built-in theme.json.
  2. Block defaults: Settings declared by blocks in their block.json files.
  3. Theme: Your theme's theme.json. If you're using a child theme, the child's theme.json is merged on top of the parent's.
  4. User: Changes made in the Site Editor's Styles panel, saved in the database.

This means users can always override your defaults in Global Styles, and a child theme only needs to include the values it wants to change.

Using theme.json in a Child Theme

A child theme's theme.json only needs the parts you want to change. For example, to change just the primary color and content width:

{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "settings": {
    "layout": {
      "contentSize": "800px"
    }
  },
  "styles": {
    "elements": {
      "link": {
        "color": { "text": "#16a34a" }
      }
    }
  }
}

One caveat: arrays such as palette are replaced, not merged. If you redefine the palette in a child theme, include every color you want to keep.

Working With theme.json in PHP

Sometimes you need theme.json data in PHP, or you want to modify it dynamically.

Reading Settings and Styles

Use wp_get_global_settings() and wp_get_global_styles():

function mytheme_get_theme_palette() {
$palette = wp_get_global_settings( array( 'color', 'palette', 'theme' ) );
return is_array( $palette ) ? $palette : array();
}

This returns the theme's palette as an array of slug, name, and color entries, merged with any overrides from child themes.

Modifying theme.json Data With a Filter

The wp_theme_json_data_theme filter lets you change theme data before WordPress uses it. This is handy in a plugin that needs to add a brand color regardless of the theme:

function sajjad_add_brand_color( $theme_json ) {
$data = $theme_json->get_data();

$palette = isset( $data['settings']['color']['palette']['theme'] )
? $data['settings']['color']['palette']['theme']
: array();

$palette[] = array(
'slug'  => 'brand',
'color' => '#e11d48',
'name'  => __( 'Brand', 'sajjad' ),
);

return $theme_json->update_with(
array(
'version'  => 3,
'settings' => array(
'color' => array(
'palette' => $palette,
),
),
)
);
}
add_filter( 'wp_theme_json_data_theme', 'sajjad_add_brand_color' );

Similar filters exist for other layers: wp_theme_json_data_default, wp_theme_json_data_blocks, and wp_theme_json_data_user.

Using theme.json in a Classic Theme

Classic themes can adopt theme.json to control the block editor. A classic theme's theme.json might include only settings:

{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "settings": {
    "color": {
      "custom": false,
      "palette": [
        { "slug": "primary", "color": "#2563eb", "name": "Primary" },
        { "slug": "dark", "color": "#111827", "name": "Dark" }
      ]
    },
    "layout": {
      "contentSize": "740px"
    }
  }
}

This replaces calls like add_theme_support( 'editor-color-palette', ... ). Keep in mind that adding theme.json to a classic theme can change some default editor behavior, so test it carefully before deploying.

Tips and Common Mistakes

Validate your JSON: A single missing comma breaks the whole file, and WordPress will silently ignore it. Using the $schema line in a code editor catches most errors.

Match slugs carefully: Styles and patterns reference presets by slug. Renaming a slug breaks anything that uses it.

Remember user overrides: If your changes don't show up, a user may have customized that setting in Global Styles. Check Styles > Revisions or reset styles to test.

Clear caches during development: WordPress caches theme.json data. With WP_DEBUG enabled, caching is relaxed, but you may still need to refresh or clear object caches.

Use version 3 for new themes: But make sure your "Requires at least" header in style.css reflects WordPress 6.6 or later.

Prefer presets over hard-coded values: Referencing presets keeps style variations and user changes working as expected.


FAQ: Using theme.json in WordPress

It goes in the root folder of your theme, next to style.css. A child theme can have its own theme.json that overrides the parent's values.

Yes. Classic themes can use theme.json to control the block editor's color palettes, font sizes, layout widths, and available design tools, even without the full Site Editor.

Use version 3 for new themes that require WordPress 6.6 or later. Version 2 remains supported if you need compatibility with older WordPress releases.

Check for JSON syntax errors first. Then check whether a user has customized the same setting in Global Styles, since user changes override the theme. Clearing caches can also help.

Yes. style.css is still required for the theme header, and you can enqueue additional CSS for anything theme.json can't express. Most design values, though, are best kept in theme.json.

Add a fontFamilies entry with a fontFace array under settings.typography, pointing the src to font files in your theme using the file:./ prefix. WordPress generates the font-face CSS automatically.

Yes. Set settings.color.custom to false. Users will then only be able to choose colors from the palette you define.


Conclusion

theme.json gives you a single, structured place to define your theme's design system. With settings, you decide which colors, fonts, spacing sizes, and tools are available. With styles, you apply them to the site, to elements like links and buttons, and to individual blocks. WordPress takes care of generating CSS variables and classes, and keeps the editor and front end looking the same.

Start with a small file that sets your palette, fonts, and layout widths, then add element and block styles as your design grows. Use style variations to offer alternative looks, child themes to adjust existing themes, and the PHP helpers and filters when you need dynamic control. Once you're comfortable with theme.json, you'll find you write far less CSS and your themes become much easier to maintain.

Tags :
Share :

Related Posts

What are the best WordPress security plugins?

What are the best WordPress security plugins?

The best WordPress security plugins for most sites are Wordfence, Sucuri Security, Solid Security, MalCare, All-In-One Security (AIOS), Patchstack, a

Dive Deeper
What is the difference between posts and pages in WordPress?

What is the difference between posts and pages in WordPress?

The main difference between posts and pages in WordPress is that posts are timely, dated entries that appear in your blog feed, archives, and RSS fee

Dive Deeper
How much does it cost to build a WordPress website?

How much does it cost to build a WordPress website?

Building a WordPress website can cost anywhere from roughly the price of a domain and a year of budget hosting, if you do it yourself with free tools

Dive Deeper