Defining Global Styles

Avatar of Ganesh Dahal
Ganesh Dahal on (Updated on )

Let’s move to the other top-level section of theme.json where we can configure the CSS of a block theme: styles. We’ll learn what it is exactly and how we can use it to override and apply the preset settings values we covered in Part 2.

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.

Table of contents


The styles section (styles)

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
PropertyStyleCSS equivalentAdditional props
bordercolorborder-color
radiusborder-radius
styleborder-style
widthborder-width
topborder-topcolorstylewidth
rightborder-rightcolorstylewidth
bottomborder-bottomcolorstylewidth
leftbottom-leftcolorstylewidth
colorbackgroundbackground-color
gradientbackground-image
textcolor
spacingblockGapgap (in Flexbox and Grid containers)
marginmarginbottom, leftrighttop
paddingpaddingbottomleftrighttop
typographyfontFamilyfont-family
fontSizefont-size
fontStylefont-style
fontWeightfont-weight
letterSpacingletter-spacing
lineHeightline-height
textDecorationtext-decoration
textTransformtext-transform
filterduotonefilter
shadowbox-shadow
outlinecoloroutline-color
offsetoutline-offset
styleoutline-style
widthoutline-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, but margin-inline-start is not as of this writing.
  • Multiple color formats are supported, such as rgba and hsla, 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.

Query Loop block in the WordPress Block Editor with a Heading 2 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 PropertyGenerated selectorUse cases
elements.buttonsButtonsButtons block, blocks
elements.headingHeadingsHeadings block,
elements.h1 to elements.h6<h1> to <h6>Site title, post title, blocks
elements.link<a>Links
elements.citeblockquote.cite, quote.citeQuote, Pullquote
elements.caption<figcaption>, <caption>Figure, Table
elements.spacing.paddingPaddingHeadings, row, blocks, paragraph
elements.typographyTypographyHeadings, 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.

The full-site template editing screen with the style variations panel open.

You can read more about building block theme style variations in this CSS-Tricks article.

Additional resources

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!