Adding and using custom settings in theme.json


One of my favorite features of theme.json is its support of custom settings. It’s one of the most powerful ways to build on top of the block system but is often underutilized by the theming community.

Unlike most other theme.json properties, custom settings aren’t tied to controls in the interface or something that users can interact with. Even the term “settings” is a bit of a stretch when thinking about how they work.

What you’re actually doing is configuring CSS custom properties (aka CSS variables) that WordPress will output in the editor and on the front end.

Let’s spend some time together exploring how custom settings work and what they let you do. Then we’ll walk through a practical example. And by the end, I hope you’ll see the possibilities and start pushing the limits of what you can give your users in your own themes.

How custom settings work

Suppose you wanted to use one global value for text shadows across your site. This might be how you’d write the CSS for it in a traditional stylesheet:

body {
	--text-shadow: 2px 2px 2px rgba( 0, 0, 0, 0.3 );
}

It’s perfectly OK to keep doing that if you want, but I recommend doing it the  WordPress way

So let’s try that out. First, add this code to your theme.json file:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"settings": {
		"custom": {
			"textShadow": "2px 2px 2px rgba( 0, 0, 0, 0.3 )"
		}
	}
}

WordPress will automatically generate the CSS custom property for you and load it on both the front-end and in the editor. The resulting CSS code will be:

body {
	--wp--custom--text-shadow: 2px 2px 2px rgba( 0, 0, 0, 0.3 );
}

The difference between your custom CSS and that generated by WordPress? The name of the CSS custom property.

That tracks with other presets that come from theme.json—custom font sizes, spacing scale, colors, and more. For example, a color from your palette would get the CSS custom property name of --wp--preset--color--{$slug}.

In a nutshell, this feature creates CSS custom properties that follow a standard naming convention.

WordPress will automatically hyphenate camel-cased properties in the generated CSS. So textShadow becomes text-shadow. Note that numbers are also treated the same as an uppercase letter.

Why follow the WordPress convention?

You may be wondering why you should follow the WordPress standard if all it does is create CSS custom properties. I mean, you can do that all on your own, right?

A good rule of thumb is to use WordPress’ built-in features whenever you can. That’s because you’ll usually get access to more features and tools. And custom settings are no different.

Let’s dive into some examples. I’m sure you’ll see the benefits!

Overrides in global style variations and child themes

Custom settings make it trivial to override the values via global style variations and child themes.

Remember the text-shadow example above? Try overriding it from a variation in your theme. Make a new /styles/example.json file and add this JSON code to it:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"title": "Example",
	"settings": {
		"custom": {
			"textShadow": "2px 2px 2px rgba( 0, 0, 0, 0.7 )"
		}
	}
}

Once you save the Example style variation via Appearance > Editor > Styles, WordPress will use the settings.custom.textShadow value from /styles/example.json and output this CSS:

body {
	--wp--custom--text-shadow: 2px 2px 2px rgba( 0, 0, 0, 0.7 );
}

And overrides are just the beginning. You can also define completely new properties for (and in) your custom variations or child themes.

Per-block custom properties

Doing things the WordPress way also lets you write custom properties for a specific block. You can even override your own global settings at the block level.

Suppose you wanted to change the text-shadow property when a Heading block is in use. To do that, target settings.blocks[core/heading].custom.textShadow. Give it a try:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"settings": {
		"custom": {
			"textShadow": "2px 2px 2px rgba( 0, 0, 0, 0.3 )"
		},
		"blocks": {
			"core/heading": {
				"custom": {
					"textShadow": "2px 2px 2px rgba( 0, 0, 0, 0.7 )"
				}
			}
		}
	}
}

WordPress will generate this CSS:

body {
	--wp--custom--text-shadow: 2px 2px 2px rgba( 0, 0, 0, 0.3 );
}

.wp-block-heading {
	--wp--custom--text-shadow: 2px 2px 2px rgba( 0, 0, 0, 0.7 );
}

Filtering theme.json data

For more advanced use cases, you have access to the settings through theme.json-related filter hooks and functions. For example, you could filter wp_theme_json_data_theme to dynamically alter the data.

Find out more at How to modify theme.json data using server-side filters.

Integrating custom settings with block plugins

Some block plugins let your theme define default values for CSS custom properties. Of course, the plugin must use custom properties and name them in a way that is compatible with theme.json.

Let’s suppose you wanted to add support for a third-party block named Super Duper. Its CSS has a couple of custom properties—to set the block’s default height and apply a CSS filter:

.wp-block-super-duper {
	height: var( --wp--custom--super-duper--height, 1rem );
	filter: var( --wp--custom--super-duper--filter, blur( 8px ) );
}

Before doing anything else, go thank that plugin author for taking themes into consideration, and for namespacing the CSS custom properties.

If that third-party block existed, you could customize its height and filter properties in your theme.json:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"settings": {
		"custom": {
			"superDuper": {
				"height": "1.5rem",
				"filter": "grayscale( 100% )"
			}
		}
	}
}

I haven’t seen many block plugins in the wild that use this technique, but there are a few roaming about. My hope is that this becomes a standard in block development.

Are you a block developer? Theme authors love it when you make it easy for them to integrate with your code. To learn more about this technique, read “A note about theme compatibility” from Styling blocks: empowering users with CSS custom properties.

A practical use case: styling form inputs

You can get pretty far with styling a few base elements and blocks in theme.json. But you are limited to the design tools you get in core WordPress. If you want to do more, that’s when you reach for custom settings.

This is actually one of my pet-peeves: theme.json doesn’t support styling form elements with the standard design features.

There are some contributors working hard on better form handling. But if you’re staring down a deadline, you can’t wait around for that to show up in a release. So let’s walk through adding some base support with settings.custom.

Create custom properties in theme.json

To keep the code examples from getting out of hand, let’s work with just a few colors. I’ll show you how to add text, background, and border colors for <input>, <select>, and <textarea> elements. And of course you’d probably extend this to typography, shadows, or anything else in the CSS specification.

First, pick out the three colors you want to use. I’m going with a mix of grays:

A login form example set on a blue background. The form itself is white with gray input fields.

Add your colors to theme.json under a custom settings.custom.formInput object:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"settings": {
		"custom": {
			"formInput": {
				"color": "#000000",
				"background": "#f1f5f9",
				"borderColor": "#e2e8f0"
			}
		}
	}
}

Did you catch something new in the code? In previous examples, you nested the custom properties and values right in the settings.custom object. This theme.json code adds another nested object that groups all the CSS custom properties for form inputs together.

And here’s the CSS that WordPress will output:

body {
	--wp--custom--form-input--color: #000000;
	--wp--custom--form-input--background: #f1f5f9;
	--wp--custom--form-input--border-color: #e2e8f0;
}

WordPress generates the property names with proper nesting. It adds all the correct levels, in order, and separates them with a double hyphen.

You can add as many levels of nesting as you want inside of settings.custom. But you probably shouldn’t take it too far. The more levels, the longer and more complex your CSS custom property names become.

Use the custom properties in CSS

The next step is putting those custom properties to use. That means sticking a bit of code in a CSS file somewhere. For this tutorial, I’ll assume that you’re loading the theme’s primary style.css file for any of your styling needs.

So add this code to your theme’s style.css:

input:where( :not( [type=checkbox] ):not( [type=radio ] ) ),
select,
textarea {
	color: var( --wp--custom--form-input--color, inherit );
	background: var( --wp--custom--form-input--background, transparent );
	border: 1px solid var( --wp--custom--form-input--border-color, currentColor );
	/* Other CSS rules... */
}

Of course, you’ll probably want to flesh that CSS out for better overall form styling. This code only shows you how to use the custom properties that you defined.

I’m testing this with the Login/out core block (while logged out so that it shows the form). Perhaps that was a poor choice in examples because its default styling—or lack thereof—is a bit messy as of WordPress 6.3.

We might as well take care of that while we’re here. Use this CSS to make the Login/out block’s layout serviceable:

.wp-block-loginout form {
	display: grid;
	grid-template-columns: repeat( 1, 1fr );
	gap: var( --wp--style--block-gap );
}

.wp-block-loginout form > * {
	margin: 0;
}

.wp-block-loginout form input:not([type=submit]):not([type=checkbox]):not([type=hidden]) {
	box-sizing: border-box;
	display: block;
	width: 100%;
}

This only fixes the block’s layout issues. I’ll leave any other style rules up to you. 

Override in a child theme or style variation

Try making some adjustments either through a child theme’s theme.json file or a global style variation in your theme’s /styles folder. Either method will yield the same result.

I added this JSON code to the theme.json in one of my child themes:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 2,
	"settings": {
		"custom": {
			"formInput": {
				"background": "#f0ebe4",
				"borderColor": "#e9e1d8"
			}
		}
	}
}

If you look closely at the code, you should see that I left out the settings.formInput.color property that was defined in the parent theme’s theme.json. This was intentional. WordPress’ theme.json system is hierarchical, so properties are inherited from the parent level in the hierarchy.

WordPress will generate this CSS, pulling data from both the parent and child theme.json files:

body {
	--wp--custom--form-input--color: #000000;
	--wp--custom--form-input--background: #f0ebe4;
	--wp--custom--form-input--border-color: #e9e1d8;
}

And forms will look like this with the child theme active:

A login form example set on a brownish-red background. The form itself is a light brown with slightly darker input fields.

This method has quickly become one of my go-to techniques whenever I add custom style rules in my theme. I always want child theme authors to have an easy way to alter those customizations. It’s a powerful feature that offers a ton of flexibility. So give it a try in your themes.

Props to @marybaum and @bph for reviewing and providing feedback.

2 responses to “Adding and using custom settings in theme.json”

  1. ProxxiM Avatar

    I used custom settings to control the border-radius of various elements in a theme which we’ll be using for multiple clients. The theme has a default style and every client gets their own json to override the default style. It’s great.

    I wish there’d be controls in the interface to manipulate the custom settings. But being able to do such a thing by just modifying a json file (and thus without a child theme) is already intriguing.

  2. ProxxiM Avatar

    I have one question. How can I refer to a custom variable in the HTML template markup language?

    I set-up some custom variables like this:
    “`
    {
    “settings”: {
    “custom”: {
    “border-rounded-full”: “9999px”,
    “border-rounded-xl”: “20px”,
    “border-rounded-lg”: “10px”,
    “border-rounded-md”: “6px”,
    “border-rounded-sm”: “2px”,
    }
    }
    }
    “`

    But what do I do with the value in “radius” in this markup to refer to the custom variable?

    “`

    “`

    Changing `15px` to `var:preset|custom|border-rounded-md` doesn’t work out.

Leave a Reply

Your email address will not be published. Required fields are marked *