Skip to content
Ramesh Gautam

theme.json in a classic WordPress theme: what it’s good for

theme.json in a classic WordPress theme: what it’s good for, a WordPress article by Ramesh Gautam

There’s a common assumption that theme.json belongs to block themes. It doesn’t. A classic PHP theme with its own templates can still ship a theme.json, and it’s one of the cheapest wins you can add to a theme that uses the block editor for content.

This site is a classic theme. Templates are PHP, the header and footer are PHP, and every homepage section is a custom block. It still has a theme.json, and here’s what it does.

One colour palette, everywhere

The palette in theme.json shows up in the editor’s colour picker, and WordPress turns every entry into a CSS custom property:

{
	"version": 3,
	"settings": {
		"color": {
			"palette": [
				{ "slug": "ink", "color": "#16202e", "name": "Ink" },
				{ "slug": "accent", "color": "#047857", "name": "Accent" }
			],
			"custom": false,
			"customGradient": false,
			"defaultPalette": false
		}
	}
}

That gives you --wp--preset--color--accent in both the front end and the editor, plus utility classes like has-accent-color. Turning off custom and defaultPalette matters as much as adding colours: editors can only pick brand colours, so nobody publishes a lime green heading by accident.

Type and spacing presets

The same applies to font sizes and spacing. Define a short scale and the editor’s controls snap to it:

"typography": {
	"customFontSize": false,
	"fontSizes": [
		{ "slug": "small", "size": "0.9375rem", "name": "Small" },
		{ "slug": "large", "size": "clamp(1.25rem, 1rem + 1vw, 1.5rem)", "name": "Large" }
	]
}

Fluid values with clamp() work fine here. The result is fewer one-off sizes in post content, which is where design drift usually starts.

Content width in the editor

settings.layout.contentSize and wideSize tell the editor how wide content and wide-aligned blocks should be. Without them, the editor shows text at whatever width the admin screen allows, and authors have no idea how a post will actually look.

"layout": {
	"contentSize": "42rem",
	"wideSize": "72rem"
}

Match these to your front-end CSS and the editor becomes a reasonable preview.

What I don’t put in theme.json

In a classic theme I keep theme.json to settings and presets. I don’t move the whole design into styles, because the theme’s CSS already does that job and two sources of truth fight each other. The rule I follow: theme.json defines the options, CSS defines the design.

I also keep runtime values out of it. This site lets the owner choose one of nine colour themes in the admin. The palette in theme.json is filtered in PHP with wp_theme_json_data_theme, so the editor always shows the active colours:

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

Checklist

  • Set version to the current schema and add the $schema URL for autocomplete in your editor.
  • Define palette, font sizes and spacing; turn off the custom and default options.
  • Set contentSize and wideSize to match your CSS.
  • Leave the visual design in your stylesheet.

If you’re deciding how to build the blocks themselves, I compared the two main approaches in SCF blocks vs React blocks.