In Part 2 of this series, we covered the process of enabling certain features in a WordPress block theme in the theme.json
file. We spent time in the settings
section of the file, outlining the various available features and how they allow us to customize the Global Styles UI in the WordPress Site Editor with preset values that map to CSS custom property values that are scoped to the <body>
element.
WordPress Block Theme CSS and Style Settings Guide
Table of contents
styles
)
The styles section (The styles
section sits alongside the settings
section we covered in the last article as a top-level section.
{
"version": 2,
"settings": {},
"styles": {
// etc.
}
}
We learned in Part 2 that the settings
section defines default styles at the top level — the CSS generally applied to the <body>
element. The styles
section is where we can override those presets with more granular styles applied to specific global elements (e.g. <h1>
) and specific blocks (e.g. theme.json
file from Twenty Twenty-Three, and other latest block themes from theme directory).
In other words, the settings
are “top-level” styles that cascade down to all elements. We can override those presets in the styles
section where we apply styles to specific elements
and blocks
. In fact, elements
and blocks
are subsections of styles
:
{
"version": 2,
"settings": {},
"styles": {
// Global element styles
"elements": {},
// Block styles
"blocks": {}
}
}
What makes this great is that theme.json
is outlined in a way that organizes styles to cascade very much the same way CSS stylesheet does. If settings
is for configuring global style features, then styles
is where we override those presets when working with specific global elements
and individual blocks
.
Supported properties
When we looked at the settings
section in Part 2, we saw that it contains “features” that can be enabled and configured in a block theme. Regarding the styles
sections, we’re defining CSS properties instead.
In fact, the objects contained in the styles
section are mapped to actual CSS properties. For example, styles.border-color
corresponds to the border-color
property in CSS.
So, before we jump into the styles
section and how to customize the appearance of a block theme, we ought to know exactly what CSS we’re working with. The following table outlines all of the CSS properties that are currently supported in the styles
section as of WordPress 6.1:
Full table of properties
Property | Style | CSS equivalent | Additional props |
---|---|---|---|
border | color | border-color | |
radius | border-radius | ||
style | border-style | ||
width | border-width | ||
top | border-top | color , style , width | |
right | border-right | color , style , width | |
bottom | border-bottom | color , style , width | |
left | bottom-left | color , style , width | |
color | background | background-color | |
gradient | background-image | ||
text | color | ||
spacing | blockGap | gap (in Flexbox and Grid containers) | |
margin | margin | bottom, left , right , top | |
padding | padding | bottom , left , right , top | |
typography | fontFamily | font-family | |
fontSize | font-size | ||
fontStyle | font-style | ||
fontWeight | font-weight | ||
letterSpacing | letter-spacing | ||
lineHeight | line-height | ||
textDecoration | text-decoration | ||
textTransform | text-transform | ||
filter | duotone | filter | |
shadow | box-shadow | ||
outline | color | outline-color | |
offset | outline-offset | ||
style | outline-style | ||
width | outline-width |
There are a couple of things worth noting when it comes to setting these properties:
- There are no logical property equivalents. For example,
margin-left
is supported, butmargin-inline-start
is not as of this writing. - Multiple color formats are supported, such as
rgba
andhsla
, but the new syntaxes are not, e.g.,rgb(255, 255, 255 / .5)
.
Top-level styles
We already covered top-level styles in Part 2. By “top-level” we mean styles that are applied globally on the root element (<html>
) as well as the <body>
. These styles are “top-level” in the sense they are inherited by everything in the theme by default.
The classic example in CSS is setting a default font size on the <body>
element:
body {
font-size: 1.125rem;
}
Every element now has a font-size
value of 1.125rem
, unless it is overridden somewhere else. You might think that top-level styles are set in the styles
section of theme.json
. But if you recall the last article in this series, top-level styles are the preset values we define in the settings
section instead:
{
"version": 2,
"settings": {
"typography": {
"fontSizes": {
"size": "1.125rem",
"slug": "medium"
}
}
},
"styles": {}
}
Behind the scenes, the WordPress Style Engine generates a CSS custom property that is applied to the <body>
element:
body {
font-size: var(--wp--preset--font-size--medium);
}
…which results in precisely what we’d expect in CSS:
body {
font-size: 1.125rem;
}
Now we can jump into the styles
section to override this sort of top-level style on two different levels: elements
and blocks
.
Element-level styles
What if we want to scope that top-level font-size
to a specific element? A global element is a little tricky to understand because they are both blocks and blocks that can be nested in other blocks.
Think of elements
as “core” blocks. That’s actually what they’re called in the WordPress Handbook, and it’s a good description of what they are: blocks that come with WordPress Core out of the box.
A heading is a perfect example of a core block. There is a Heading block we can use to add any heading level (<h1>
–<h6>
) to a page or post. So, if you need to drop a Heading 2 element on a page, you have a specific block to do that.
But a Heading 2 might be part of another block. For example, if you were to add a Query Loop block to a page, you would get a list of posts, each with a Post Title block that outputs an <h2>
element.
If you want to style all heading elements, regardless of level, you can do it in the elements
object in theme.json
:
{
"version": 2,
"settings": { }
"styles": {
// Global-level styles
"elements": {
"heading": { ... },
}
}
}
Let’s say the CSS color
property is set to black as a top-level style in settings
:
{
"version": 2,
"settings": {
// Top-level styles
"color": {
"palette": {
"color": "#000000",
"name": "Contrast",
"slug": "contrast"
}
}
},
}
The WordPress Style Engine creates a CSS custom property that can be applied to the <body>
at the top level:
body {
color: var(--wp--preset--color--contrast);
}
Maybe you want all your headings to be a different color than what is already applied to the <body>
element. You can set that on styles.elements.heading
to override black with, say, a dark gray color:
{
"version": 2,
"settings": {
// Top-level style presets
"color": {
"palette": {
"color": "#000000",
"name": "Contrast",
"slug": "contrast"
}
}
},
"styles": {
// Global-level styles
"elements": {
"heading": {
"color": {
"text": "#333333"
}
}
}
}
}
Another way to go about it is to configure a new color in settings.color.palette
and apply the generated CSS custom property to styles.elements.heading.color.text
.
OK, but maybe you want the Heading 2 global element to “pop” more than other heading levels. You can override the dark gray for all the core headings element with another color value assigned to the h2
element:
{
"version": 2,
"settings": {
// Top-level style presets
"color": {
"palette": {
"color": "#000000",
"name": "Contrast",
"slug": "contrast"
}
}
},
"styles": {
// Global-level styles
"elements": {
"heading": {
"color": {
"text": "#333333"
}
},
"h2": {
"color": {
"text": "#F8A100"
}
}
}
}
}
At the time of this writing, the following global elements are currently supported in the styles.elements
section:
JSON Property | Generated selector | Use cases |
---|---|---|
elements.buttons | Buttons | Buttons block, blocks |
elements.heading | Headings | Headings block, |
elements.h1 to elements.h6 | <h1> to <h6> | Site title, post title, blocks |
elements.link | <a> | Links |
elements.cite | blockquote.cite , quote.cite | Quote, Pullquote |
elements.caption | <figcaption> , <caption> | Figure, Table |
elements.spacing.padding | Padding | Headings, row, blocks, paragraph |
elements.typography | Typography | Headings, paragraph |
Block-level styles
There’s yet one more level in styles
, and it’s used to customize the CSS for individual blocks:
{
"version": 2,
"styles": {
// Top-level styles
// etc.
// Global-level styles
"elements": { },
// Block-level styles
"blocks": { }
}
}
Let’s go ahead and pick up right where we left off in the last section. We had set the color
property to black in the top-level styles
, overrode that with a different color for all headings in styles.elements.heading
, then overrode that just for the Heading 2 element on styles.elements.h2
. Here’s that code again:
{
"version": 2,
"styles": {
// Top-level styles
"color": "#000000",
// Global-level styles
"elements": {
"heading": {
"color": {
"text": "#333333"
}
},
"h2": {
"color": {
"text": "#f8a100"
}
},
}
}
}
Earlier, we discussed how a global element, like Heading 2, can also be part of another block. We used the Query Loop block as an example, where Heading 2 is used for each post title.
So far, the color of the Query Loop’s post title would be #F8A100
because that is what is set on styles.elements.h2
. But you can override that in the styles.blocks
section if you want to set the Query Loop block’s Heading 2 element to yet another color without interfering with other headings:
{
"version": 2,
"styles": {
// Top-level styles
"color": "#000000",
// Global-level styles
"elements": {
"heading": {
"color": {
"text": "#333333"
}
},
"h2": {
"color": {
"text": "#F8A100"
}
}
},
"blocks": {
"core/query": {
"elements": {
"h2": {
"color": {
"text": "var(--wp--preset--color--primary)"
}
}
}
}
}
}
}
Behind the scenes, the WordPress Style Engine creates the CSS for the Query Loop’s <h2>
element:
.wp-query h2 {
color: var(--wp--preset--color--primary);
}
Pretty great, right? Now we have a way to set default styles and override them at various levels in the theme in a structured way that works much like the CSS Cascade.
Check out the WordPress Handbook for a complete list of blocks that are available to use in the Block Editor.
Interactive styles
What if you want to customize the CSS for different interactive states? Everything we’ve looked at so far is great for styling a core block, like Button, but what about the styles when someone hovers their cursor over a Button block? Or maybe when that button is in focus?
We can do that with CSS pseudo-classes. You’re probably already well-versed in using pseudo-classes like :hover
, :focus
, :active
, and :visited
. They’re super common!
Thankfully, support for styling interactive states for certain core blocks, like buttons and links, using pseudo-classes gained theme.json
support in WordPress 6.1. Here’s an example pulled from one of my previous articles that adds a box-shadow
on a theme’s Button elements on hover:
{
"version": 2,
"settings": {},
"styles": {
"elements": {
"button": {
":hover": {
"shadow": "10px 10px 5px 0px rgba(0,0,0,0.66)"
}
}
}
}
}
The WordPress Style Engine reads this and generates the following CSS:
.wp-button:hover {
box-shadow: 10px 10px 5px 0px rgba(0,0,0,0.66);
}
But before you rush off to create all your interactive styles, you’ll want to note that theme.json
currently supports just the :hover
, :focus
, and :active
interactive pseudo-classes as of WordPress 6.1, and even those are limited to the button
and link
elements. But that is likely to expand to others in the future — including pseudo-classes specific to form elements — as noted in this Make WordPress Core blog post introducing interactive styles.
So, for now, here is an example of the most complete way to customize the interactive styles of a button.
{
"version": 2,
"styles": {
"elements": {
"button": {
"color": {
"background": "#17A2b8",
"text": "#ffffff"
}
":hover": {
"color": {
"background": "#138496"
}
},
":focus": {
"color": {
"background": "#138496"
}
},
":active": {
"color": {
"background": "#138496"
}
}
}
}
}
}
The same thing works if we were to change elements.button
to elements.link
.
One more JSON object is mentioned in the WordPress Handbook called css
but there is no documentation for it other than mentioning it is used to set custom CSS that is not handled by other theme.json
properties.
Referencing styles
A referencing style feature also available in theme.json
. This allows you to refer to a previously defined root-level style property using the ref:
term.
In the earlier top-level style example, we have registered the following background color using the styles.color.background
property at the root of the styles
property.
"styles": {
"color": {
"background": "var(--wp--preset--color--base)"
}
}
We can reuse the same style property to any number of blocks:
{
"color": {
"text": { ref: "styles.color.background" }
}
}
Global style variations
It’s possible for a theme to include more than one theme.json
file. Why? Because that’s how you can make Global Styles variations.
That link will take you to the formal documentation for Global Styles variations, but the general idea is this: theme.json
contains all your default values and sits in the theme folder’s root directory, and additional theme.json
files are added to the theme folder that are “variations” of the default theme.
Here’s how it works. Say we have finished configuring our default theme.json
file and it’s sitting in the theme’s root directory. Now say you want to implement a “dark mode” feature that allows the user to toggle colors between light and dark palettes. We’d make a new file — maybe call it dark.json
and place it in a new folder in the theme called /styles
. We can add as many of these files as we want to create all the variations your heart desires.
theme-folder/
|__ /assets
|__ /patterns
|__ /templates
|__ /parts
|__ /styles
|__ dark.json
|__ index.php
|__ functions.php
|__ style.css
|__ theme.json
The biggest difference between the default theme.json
file and our dark.json
variation file is that our variation file includes a title
field that allows us to differentiate it from other JSON files.
{
"version": 2,
"title": "Dark",
"styles": {}
}
Anything we put in here overrides what’s in theme.json
once it is activated. And how do you activate a global styles variation? You’ll find it in the Site Editor (Dashboard → Appearance → Editor) under “Browse Styles” (Editor → Styles → “Browse Styles”). That’s the secret behind all the different variations offered in the default Twenty Twenty-Three theme.
You can read more about building block theme style variations in this CSS-Tricks article.
Additional resources
- Global Styles & theme.json (Full Site Editing With WordPress)
- Global Settings & Styles (theme.json) (WordPress Developer Resources)
- Updates for Settings, Styles, and theme.json (Make WordPress Core)
- Standardizing Theme.json Color Slugs in WordPress Block Themes (Rich Tabor)
- Curating the Editor Experience (WordPress Block Editor Handbook)
- 3 Ways to Curate the Editor Experience in WordPress (WP Engine Blog)
- Video: How to curate the editing experience (Learn WordPress)
Wrapping up
We are really close to having a more complete picture of the theme.json
file. We’ve covered the structure, how to enable features, and how to configure styles at the theme, global, and block level, even going so far as to look at custom styling capabilities and overriding all of our defaults with global styles variations. Hopefully you see just how powerful theme.json
is in the block theme era of WordPress.
The next article in this series will give you a deeper look at the Style Engine. We’ve referred to it several times but have only briefly described what it does. It’s an integral piece of the block theme puzzle because it transforms all the JSON we write into vanilla CSS code.
It is the single source of truth when it comes to managing a block theme’s CSS. And it’s a good idea to know what WordPress is doing behind the scenes when a theme is rendered, so let’s move on to the next article!