theme.json

This article is part of the WordPress guide. Read the introduction.

theme.json is a global theme configuration file. It tells WordPress what settings you want to enable, provides styles for specific elements, lets you register templates and template parts, and more. It’s a step up from having to configure your theme with function calls and hooks in the functions.php file.

There are 7 top-level properties that can be configured. Here’s what the “empty” file looks like:

JSON
{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 3,
	"settings": {},
	"styles": {},
	"customTemplates": {},
	"templateParts": {},
	"patterns": []
}
  • $schema – defines supported JSON schema. Can be used by code editors to provide hints and error reporting.
  • version – the theme.json schema version. The latest version (as of August 2025) is 3. The theme.json Living Reference is the most up-to-date document on theme.json.
  • settings – defines block controls, color palettes, layouts, font sizes, and more.
  • styles – applies styles to the website and blocks.
  • customTemplates – metadata for custom templates.
  • templateParts – metadata for template parts.
  • patterns – allows you to bundle patterns from the Patterns Directory with your theme.

Bear in mind, theme.json is a huge topic. There are dozens if not hundreds of different properties you can configure. Listing and explaining all of them in detail in this guide would be neither realistic nor helpful. The following list is limited to only the most important properties and values. Read the official theme.json documentation and the aforementioned Living Reference if you need a more in-depth view.

settings

The settings property configures, as the name implies, settings. Let’s look at the top level structure first and break some of it down later.

JSON
{
	"version": 3,
	"settings": {
		"appearanceTools": false,
		"background": {},
		"border": {},
		"color": {},
		"custom": {},
		"dimensions": {},
		"layout": {},
		"lightbox": {},
		"position": {},
		"shadow": {},
		"spacing": {},
		"typography": {},
		"useRootPaddingAwareAlignments": false,
		"blocks": {}
	}
}

You might be a little confused here. Most of these properties sound like they should be placed in the styles property instead. An important thing to understand is that the settings properties are usually responsible for either enabling (and disabling) functionality in the editor or registering presets (e.g., color presets). These settings provide the tools, and the tools are then utilized by users and styles.

Most properties in settings are boolean properties. They are either true or false and they control the behavior of the website in some way. Let’s look at some (not all) of these properties so that you get the idea:

  • background.backgroundImage – allow users to set a background image.
  • border.color – allow users to set custom border colors.
  • border.radius – allow users to set custom border radius.
  • color.background – allow users to set background colors.
  • color.text – allow users to set text colors in a block.
  • color.custom – allow users to select custom colors (with a color picker instead of a predefined set of colors).
  • dimensions.minheight – allow users to set custom minimum height.
  • position.sticky – allow users to set sticky position.
  • spacing.margin – allow users to set a custom margin.
  • spacing.padding – allow users to set a custom padding.
  • spacing.customSpacingSize – allow users to set custom space sizes.
  • typography.customFontSize – allow users to set custom font sizes.
  • typography.fontStyle – allow users to set custom font styles.
  • typography.letterSpacing – allow users to set custom letter spacing.

These are only a few of the properties defined in theme.json v3. It’s important to understand what really happens when you set one of them. Let’s take position.sticky as an example. Just because you set it to true doesn’t mean every block will now have the ‘sticky’ checkbox. It only signals to WordPress that it can show this checkbox.

This difference is crucial so it’s important you pay attention. The block author defines what attributes and options the block supports. You should know that already if you read the blocks section. The theme author, using theme.json, can enable or disable support for certain options.

If the block supports position.sticky, but the theme disabled it, it won’t be shown. If the block doesn’t support position.sticky, and the theme enabled it, it still won’t be shown. It will only be shown if the block supports position.sticky and the theme enables it.

It’s important to remember that most of these properties have some default values. This will be ‘true’ for some and ‘false’ for others. Consult the documentation for more info. Let’s now look at a few important, non-boolean properties.

color.palette, color.duotone, and color.gradients let you register custom color presets. Palette is for normal colors, duotone are dual colors, usually used for image filters (shadow and highlight), and gradients are gradients. These properties expect an array with a ‘name’, ‘slug’, and some values. Here’s an example:

JSON
{
	"version": 3,
	"settings": {
		"color": {
			"palette": [
				{
					"color": "#ffffff",
					"name": "Base",
					"slug": "base"
				},
				{
					"color": "#000000",
					"name": "Contrast",
					"slug": "contrast"
				},
			],
			"gradients": [
				{
					"gradient": "linear-gradient(to right, #10b981, #64a30d)",
					"name": "Emerald",
					"slug": "emerald"
				}
			],
			"duotone": [
				{
					"colors": [
						"#450a0a",
						"#fef2f2"
					],
					"name": "Red",
					"slug": "red"
				}
			]
		}
	}
}

This triplet of {value, name, slug} is common in settings. You will see it in many other properties such as border.radiusSizes, dimensions.aspectRations, shadow.presets, and more. All of these allow you to register presets.

layout.contentSize is an important property. It sets the max-width of content. It is typically used for controlling the width of the post content and other similar areas on the page. This should be set to a value which makes the content readable (a line of text should usually be between 45 and 75 characters wide).

spacing.units is another important property. It expects an array of strings and allows you to specify supported units for spacing-related attributes. The default value is [“px”,”em”,”rem”,”vh”,”vw”,”%”].

spacing.spacingScale lets you define a custom spacing scale. What’s a spacing scale? It’s just a way of defining a spacing system (spacing presets). I imagine that didn’t clarify much… Let’s start from the beginning.

Many web designers use a standard scaling system for spaces. These are just pre-defined values for spaces expressed as steps, such as: 4px, 8px, 16px, 24px, 40px, 64px, etc. The designer then doesn’t apply a random number when choosing a margin for an element, but instead picks from this pre-defined set of spaces. That’s a spacing system.

spacing.spacingScale has 5 properties:

  • operator – either + (addition) or * (multiplication). Defines how the steps get calculated (default *).
  • increment – a number used with the operator to calculate the next step (default 1.5).
  • steps – the total number of steps in the scale (default 7).
  • mediumStep – the medium (center) value of the scale (default 1.5).
  • unit – a CSS unit (default rem).

The spacing system is then automatically generated using these values. What really gets generated is CSS variables following this naming convention: –wp-preset–spacing–{step}. Steps are incremented by 10. The mediumStep defines what values the rest of the steps are going to have. Let’s look at what’s generated with the default values.

–wp-preset–spacing–50 is the middle step. –wp-preset–spacing–60 (the step after) is calculated by multiplying 1.5 rem (mediumStep value) by 1.5 (increment value). That gives us 2.25 rem. –wp-preset–spacing–70 is then calculated by doing 2.25 rem * 1.5 = 3.38 rem. Similarly, –wp-preset–spacing–40 is calculated by doing 1.5 rem / 1.5 = 1 rem. We’re going the other direction from the middle so we have to divide instead of multiplying.

And here’s what these steps then look like in the Block Editor. They are sliders allowing you to choose one of the steps. Note that the middle step will always be 50, and the lowest step is 10. This means that if you have more than 10 steps, they will be cut off on the lower end of the scale. Couple that with the limiting nature of mathematically generated systems, and you might sometimes need something more precise.

spacing.spacingSizes allows you to specify all of the steps by hand. Here’s an example:

JSON
{
	"version": 3,
	"settings": {
		"spacing": {
			"spacingSizes": [
				{
					"name": "Step 1",
					"size": "4px",
					"slug": "10"
				},
				{
					"name": "Step 2",
					"size": "8px",
					"slug": "20"
				},
				{
					"name": "Step 3",
					"size": "16px",
					"slug": "30"
				},
				{
					"name": "Step 4",
					"size": "24px",
					"slug": "40"
				}
			]
		}
	}
}

This will, once again, generate –wp-preset–spacing–{step} variables. The step in this case is the slug property. The name is the label used for the step (you could see “Small” in the screenshot above). You’re also not limited to incrementing your slug by 10 or even making it a number (although both of these are good practice).

The custom property is a unique property which allows you to create custom CSS variables. These variables follow this naming convention: –wp–custom–{key}, where key is transformed to kebab-case. It makes the most sense to start with an example:

JSON
{
	"version": 3,
	"settings": {
		"custom": {
			"lineHeight": "1.4"
		}
	}
}

This will create the following CSS:

CSS
body {
	--wp--custom--line-height: 1.4;
}

Custom variables can also be nested indefinitely. This means you can do:

JSON
{
  "version": 3,
	"settings": {
		"custom": {
			"lineHeight": {
				"xs": "1",
				"sm": "1.25",
				"md": "1.5",
				"lg": "1.75"
			}
		}
	}
}

And it will create this CSS:

CSS
body {
	--wp--custom--line-height--xs: 1;
	--wp--custom--line-height--sm: 1.25;
	--wp--custom--line-height--md: 1.5;
	--wp--custom--line-height--lg: 1.75;
}

The blocks property is another unique property. All of the settings we’ve been talking about were global settings. They affected all of the blocks. The blocks property lets you modify settings for specific blocks. For example, you could create specific color presets for the button block by setting the “settings.blocks.core/button.color.palette” property. These settings overwrite the global settings (the colors will overwrite the global colors specified with settings.color.palette).

styles

The styles property is the central place for styling block themes. That’s in stark contrast with classic themes, which were styled using style.css or other CSS files. If you can, you should style your theme this way (in theme.json). This allows the user to modify those styles in the Site Editor (explained later).

The pre-defined styles properties are direct counterparts to the most used CSS properties. If the property you need doesn’t exist, you can use the css property to write the CSS by hand. The styles property lets you target 3 types of entities: root, elements, and blocks.

Root is the <body> element. All styles applied directly to the styles property target the root. Elements are HTML elements. The currently supported elements are: button (<button> and button-like links), caption (<figcaption>), cite (<cite>), heading (any <h{x}>), h1-h6 (individually), and link (<a>). Blocks let you style individual blocks.

Here’s what an “empty” styles property might look like:

JSON
{
	"version": 3,
	"styles": {
		"elements": {},
		"blocks": {}
	}
}

Let’s assume we want to set the background color of the entire website to #fefefe. We would do that on the <body> element. We also want all buttons to have a red background and a white text. Lastly, we want our image blocks to have slightly rounded corners. Here’s what our theme.json file might look like:

JSON
{
	"version": 3,
	"styles": {
		"color": {
			"background": "#fefefe"
		},
		"elements": {
			"button": {
				"color": {
					"text": "#ffffff",
					"background": "red"
				}
			}
		},
		"blocks": {
			"core/image": {
				"border": {
					"radius": "6px"
				}
			}
		}
	}
}

This will automatically generate all the necessary CSS. You can also style pseudo-classes, such as :hover. To do that, just use the pseudo-class after the element, such as “styles.elements.button.:hover.color” to change the color of the button on hover.

You can style elements nested in blocks. That is – if you have a block which has a <button> element in its markup, you can target only the button element by doing: “styles.blocks.block/name.elements.button”. You basically have to put the elements property inside the block property.

Remember Block Styles from the section about blocks? You had to register them either in PHP or in JS, and they added a Styles select in the block editor. Selecting a style there resulted in an ‘is-style-{name}’ CSS class being added to the block. You can style those in theme.json as well. All you have to do is add a variations property to the block. So to style the ‘outline’ Block Style of the button block, you’d have to target styles.blocks.core/button.variations.outline.

In the example above, we used hard-coded values. This is not how you’d usually write your styles. You’d usually use presets. You already know presets. You’ve registered them in the settings property. When creating the spacing system – you registered presets. When defining the color palette – you again registered presets.

All presets from the settings property create a corresponding CSS property (variable). This is –wp-preset–spacing–50 for the spacing step, and –wp-preset–color–base for a color with slug “base”. The general rule is –wp-preset–$feature–$slug.

To use presets in the styles properties, you should follow a specific syntax. To use the base color, you would write “var:preset|color|base”. This will use the –wp-preset–color–base variable. You could technically use the CSS native var(), but the WordPress way is preferred. This means we could do:

JSON
{
	"version": 3,
	"styles": {
		"background-color": "var:preset|color|base",
		"fontFamily": "var:preset|font-family|default"
	}
}

What about custom CSS variables (defined in the custom property)? These weren’t presets. The final CSS variable looked like this: –wp–custom–line-height–lg. You can reference those by doing “var:custom|line-height|lg”. Note that you can’t reference any arbitrary CSS variable using this notation. Only “var:preset” and “var:custom” are defined.

customTemplates

I promised we’ll talk about registering custom templates when we cover theme.json. Well, here we are. Just to remind you, these are templates for pages and other post types that the user can manually select for the post.

It’s not anything hard, and it’s certainly not going to be surprising to you if you’ve been following this guide meticulously. Here’s the code:

JSON
{
	"version": 3,
	"customTemplates": [
		{
			"name": "2-columns-layout",
			"title": "2 Columns Layout",
			"postTypes": [
				"page",
				"post",
				"book"
			]
		}
	]
}

The name property is the name of the file, i.e., /templates/2-columns-layout.html.

templateParts

You may be thinking “But I thought you didn’t have to register a template part???”. You are correct – you don’t, but you can. Here’s what it looks like:

JSON
{
	"version": 3,
	"templateParts": [
		{
			"area": "footer",
			"name": "footer-2-columns",
			"title": "Footer 2 Columns"
		},
	]
}

Okay, so what the hell is this? Well, the name is the file name. It means the part is located at /parts/footer-2-columns.html. The title is displayed in the Site Editor. This is where the first benefit of registering parts becomes apparent. If you were to not register your part but only placed them in the /parts folder, the name of this part shown to the users would be “footer-2-columns”.

But what is ‘area’? In the Site Editor, template parts are categorized by areas. That’s literally it, a category for parts. There are 3 areas defined by default: footer, header, and uncategorized. You can register custom areas, but that’s advanced and pretty niche so I won’t cover it. The area doesn’t affect the part at all, only how it’s displayed in the admin panel (in the Site Editor).

Style Variations

Perhaps a more fitting name for this section would be “theme.json variations”. WordPress lets you provide multiple versions of the theme.json file. These versions can provide vastly different styling and behavior of your theme. Let’s see how that works.

The main theme.json file should be placed in the root of your theme folder. This file will be the default configuration file of your theme the first time the user activates it. Style variations are other .json files. These files should be placed in the /styles folder.

They shouldn’t be named theme.json, but they can contain every ‘settings’ and ‘styles’ property theme.json can. So why do they exist? They are alternative theme.json files your users can select in the Site Editor.

When the user selects one of your style variations, the options defined in that JSON file are stored in the database. They are then used in place of the default theme.json configuration. This allows you to provide multiple different styles bundled with your theme. Let’s say you created a theme for restaurants. You could provide a theme.json file optimized for cafes, one for pubs, and one for high-end restaurants.

Note that the original theme.json file is still used. If a property is defined in theme.json but is not defined in a style variation, it’ll be used even if the style variation is selected. Style variations can overwrite properties from theme.json and add new properties, but they don’t cause theme.json to be ignored. If you want to disable some style or setting from theme.json in your style variation, you have to do it explicitly.

The last difference between style variations and theme.json is the top-level title property. You need it to define the title of the variation to be displayed in the Site Editor. Let’s create an “empty” variation:

JSON
{
	"version": 3,
	"title": "High-End Restaurant",
	"settings": {},
	"styles": {}
}

You could then place this in a /styles/high-end.json file.