In the last article in this series, we went through theme.json
presets and style section configurations. Then we discussed how the WordPress Style Engine processes the JSON into CSS and consolidates it with consolidated data received from different sources — WordPress Core styles, theme styles, and user styles — to create a global stylesheet. We also learned how to apply the generated CSS to individual elements, like headings, and specific blocks, like the Query Loop.
WordPress Block Theme CSS and Style Settings Guide
The Style Engine is responsible for structuring and organizing your CSS into a single global stylesheet, and understanding how it parses your theme.json
file and styles coming from other sources is rather important. Otherwise, you might find yourself in a situation where you’re unsure why one of your styles isn’t working. This way, you’ll have an idea of where to dig in and troubleshoot without being in the dark.
I discuss the WordPress Style Engine in another article. That’s a great place to start if you’re looking for a little more context without all the nitty gritty details we’re covering here.
Table of contents
Where styles come from
The Style Engine has to get its information from somewhere, right? The truth is that there are several places where the Style Engine looks for information before it generates CSS.
- WordPress Core: That’s right, WordPress ships with pre-defined styles right out of the box. Think of these as default styles authored at the CMS level, sort of like the default styles in browsers. They are there by default, but we can override them in our
theme.json
file. - The
theme.json
file: We’ve covered this file thoroughly throughout the series. It’s where we enable certain styling features and define default styles, such as colors, fonts, and spacing. You can think of it like thestyle.css
file in a “classic” WordPress theme: the styles we author at the theme level. - User styles: WordPress has a number of features that allow you to set styles directly in WordPress without touching code. The “big” one of which is the Global Styles panel in the Site Editor. It uses the styles from WordPress Core and
theme.json
as default settings, but changing the style settings overrides the other two sources.
Can you sort of see how this forms a hierarchy? WordPress Core styles can be overridden by theme.json
, and both theme.json
and WordPress Core styles can be overridden by user styles. It’s structured a lot like the CSS Cascade, in a way!
Combining sources
Just so we have a good picture of what we’re working with, here’s a screenshot of the WordPress Site Editor with the Global Styles settings on display.
This is the interface where user styles come from. Notice how there is a tile at the top of the panel that provides a preview of the theme’s current typography and colors. That tile is called a style variation, which is basically preset style configurations that are defined in JSON in the theme folder. A theme can have any number of pre-defined style variations, and choosing one instantly swaps the theme’s fonts and colors with the selected tile.
So, already, you can see how the user styles are informed by theme.json
. But notice the three categories of style settings below the style variations: Typography, Colors, and Layout. Clicking into any of those settings allows you, as the user, to override the configuration of the currently selected style variation.
Now you see how theme.json
and WordPress Core styles are overridden by user styles. We can lean on WordPress Core styles for basic layout and styling, customize them and define our own styles in theme.json
, then let the user take greater control of things with user styles from the Site Editor’s Global Styles settings.
All of this styling data is stored and parsed as JSON. The Style Engine generates that JSON and then consolidates it into a single global stylesheet for the theme.
theme.json
Processing During the consolidation phase, the Style Engine takes a look at the theme.json
file located in the theme directory. We talked earlier about the structure of theme.json
and how it is organized into two “top-level” sections, settings
, and styles
.
{
"version": 2,
// Top-level settings
"settings": {},
"styles": {
// Global element styles
"elements": {},
// Block styles
"blocks": {}
}
}
The Style Engine processes those sections like this. First, the top-level settings
generate CSS variables that are applied to the <body>
element (e.g. --wp--preset--<category>-<slug>
):
{
"version": 2,
"settings": {
// Top-level settings
"color": {
"palette": [
{
"color": "#F8A100",
"name": "Primary",
"slug": "primary"
}
]
}
},
"styles": {
// Global element styles
"elements": {},
// Block styles
"blocks": {}
}
}
In this example, we get a new CSS variable, --wp-preset--color--primary
. The Style Engine then applies it to new CSS classes with a .has-
prefix:
body {
--wp--preset--color--primary: #F8A100;
}
.has-primary-color {
color: var(--wp--preset--color--primary) !important;
}
.has-primary-background-color {
background-color: var(--wp--preset--color--primary) !important;
}
.has-primary-border-color {
border-color: var(--wp--preset--color--primary) !important;
}
Neat, right? These classes can now be used anywhere in the theme!
Next, the styles.elements
object generates element selectors matching the HTML element they represent (e.g. styles.elements.h2
corresponds with the <h2>
element).
{
"version": 2,
"settings": {},
"styles": {
// Global element styles
"elements": {
"h2": {
"color": {
"text": "#F8A100"
}
}
},
// Block styles
"blocks": {}
}
}
In this example, an h2
selector is generated with a #F8A100
color value:
h2 {
color: #F8A100;
}
And, hey, we could have used the CSS variable we defined earlier in settings
as the color value, knowing the Style Engine is going to generate it:
{
"version": 2,
"settings": {},
"styles": {
// Global element styles
"elements": {
"h2": {
"color": {
"text": "var(--wp--preset--color--primary)"
}
}
},
// Block styles
"blocks": {}
}
}
…which gives us this in CSS:
h2 {
color: var(--wp--preset--color--primary);
}
And, finally, the settings.blocks
object uses the concatenation of the block and element selector. This way, the specificity for these selectors is higher, giving them greater priority over the other generated styles we’ve seen so far.
For example, let’s change the color of the Site Title block:
{
"version": 2,
"settings": {},
"styles": {
// Global element styles
"elements": {},
// Block styles
"blocks": {
"blocks": {
"core/site-title": {
"color": {
"palette": [
{
"color": "#F8A100",
"name": "SF Orange",
"slug": "sf-orange"
}
]
}
}
}
}
}
}
Here’s how that shakes out in CSS. The Style Engine generates a class for the Site Title block (.wp-block-site-title
) and for the color as a descendant combinator selector:
.wp-block-site-title .has-sf-orange-color {
color: var(--wp--preset--color--sf-orange) !important;
}
.wp-block-site-title .has-sf-orange-background-color {
background-color: var(--wp--preset--color--sf-orange) !important;
}
.wp-block-site-title .has-sf-orange-border-color {
border-color: var(--wp--preset--color--sf-orange) !important;
}
Generated CSS classes
So, now we have a pretty good idea of how the WordPress Style Engine processes data it receives from various sources into a block theme’s CSS.
We also got to see how the settings and styles from theme.json
produce CSS variables and classes. What you’re probably interested in now is what other CSS classes do we get from Style Engine. Let’s dig into those.
Block classes
A “block” in WordPress is a standalone component that can be dropped into any page or post. Every block gets a CSS class that is used as the block’s parent container.
And all of the class names have a wp-block
prefix. For example, the Cover block gets a .wp-block-cover
class that we can use to select and style the entire block. WordPress calls these semantic classes because they identify the block in the name.
Blocks get another class in addition to its semantic class, called its stateful class. These classes describe the block’s state, as if it “has” a certain text color, or “is” a certain background color or layout type.
For example, let’s say we add a Post Title block to one of our theme’s templates in the Site Editor. But then we change it, so it has a background color, the “SF Orange” color we defined in an earlier example.
This results in a stateful class on the element. Here’s the HTML:
<h1 class="wp-block-post-title has-background has-sf-orange-background-color">
<a href="#" target="_self">Hello world!</a>
</h1>
See the two stateful CSS classes this made?
.has-background
: This adds padding to the element, so the Post Title does not bump the edges of the container, allowing more background to show..has-sf-orange-background-color
: This applies the CSS variable for the color.
Here is a table of selected WordPress blocks and examples of the sorts of classes that are generated and applied to them.
Block | Semantic Class | Stateful Class |
---|---|---|
Buttons | .wp-block-buttons | .has-custom-width |
Cover | .wp-block-cover | .is-light.has-parallax .has-position-vertical |
Columns | .wp-block-columns | .has-2-columns .has-background |
Heading | .wp-block-heading | .has-text-color |
Gallery | .wp-block-gallery | .has-nested-images |
Image | .wp-block-image | .alignleft .aligncenter .alignright .has-custom-border |
Spacer | .wp-block-spacer | .is-style-dots .has-text-color |
Quote | .wp-block-quote | .is-layout-constrained |
Again, this is not an exhaustive table of blocks. You can find a complete list, however, over at the WordPress Block Editor Handbook. If there is a complete list of stateful classes living somewhere, I could not find it. So, while the stateful class examples in the table are accurate based on my testing, it should also not be considered a complete list of classes.
Layout classes
WordPress provides different layout types that can be applied to container-based blocks. By that, we’re talking about the following blocks:
- Columns
- Group
- Post Content
- Query Loop
Each of these blocks can be assigned a layout type, and there are three options:
- Flow layout: Adds vertical spacing between nested blocks in the
margin-block
direction. Those nested blocks can also be aligned to the left, right, or center. - Constrained layout: Same exact deal as a Flow layout, but with width constraints on nested blocks that are based on the
contentWidth
andwideWidth
settings (either intheme.json
or Global Styles). - Flex layout: This was unchanged in WordPress 6.1. It uses CSS Flexbox to create a layout that flows horizontally (in a row) by default but can flow vertically as well, so blocks stack one on top of another. Spacing is applied using the CSS
gap
property.
Depending on the settings selections, this corresponds to the following CSS classes:
.is-layout-flow
.is-layout-constrained
.is-layout-flex
See how the stateful naming convention is carried over to these? These aren’t the only layout-related classes, though. Here are all of the available classes, as documented in the WordPress Block Editor Handbook:
Justin Tadlock has an excellent article that explains these layout types and semantic classes with use cases and examples. You can also refer to my article, “Using The New Constrained Layout In WordPress Block Themes”, for even more information on using different layouts.
Additional sources of user styles
We already know we can use the Global Styles settings in the Site Editor to override the CSS styles that come from WordPress Core and theme.json
. We called those user styles.
But that’s not the only place where user styles can come from. For example, let’s say you’re writing a new post and want to style a specific paragraph a certain way. And let’s say you have a CSS class that you either defined theme.json
or perhaps even your own stylesheet! As long as the CSS for those classes is loaded on the page, you can add them to any block in the block’s Advanced settings.
This Add Additional CSS Classes to Blocks guide walks you through using custom CSS classes on your site.
There’s another place where user styles can go. As of Gutenberg 14.8, a new custom “Additional CSS” box has been added to the Global Styles settings.
And, hey, a big heads-up: The CSS from these user-style sources can override or even remove the CSS settings in theme.json
. Another big thing to know is that whatever styles you define here could get lost when changing themes. It’s probably better to actually create these styles in theme.json
or by enqueuing your own stylesheet.
Block stylesheets
There’s one more place where the WordPress Style Engine might get some styling data, and that’s from your own stylesheets! That’s right: you can add your own stylesheets to a theme.
Sure, you could also use the required style.css
file as your stylesheet. Most block themes won’t be using it anyway. But there’s an even more efficient way to go about it: to enqueue stylesheets for specific blocks.
First off, you may already be familiar with the concept of enqueuing files from working with classic WordPress themes and the wp_enqueue_style()
function. That loads a CSS file by providing a path where WordPress can find it.
We can do the same thing but on a block-by-block basis. Each WordPress block has its own associated stylesheet, and we can enqueue our own stylesheets for them. For example, here I am adding a stylesheet of custom styles for the Quote block in my theme’s functions.php
file:
add_action( 'init', 'emptytheme_enqueue_block_styles' );
function emptytheme_enqueue_block_styles() {
wp_enqueue_block_style( 'core/quote', array(
'handle' => 'emptytheme-block-quote',
'src' => get_theme_file_uri( "assets/blocks/quote.css" ),
'path' => get_theme_file_path( "assets/blocks/quote.css" )
) );
}
Check that out — it’s practically the same way we’d load custom stylesheets in a classic WordPress theme, PHP and all.
If you want to add styles to multiple blocks, you need to enqueue CSS files using an array and then loop through them. The WordPress Developer Blog has a nice example written by Justin Tadlock:
add_action( 'init', 'themeslug_enqueue_block_styles' );
function themeslug_enqueue_block_styles() {
// Add the block name (with namespace) for each style.
$blocks = array(
'core/button'
'core/heading',
'core/paragraph'
);
// Loop through each block and enqueue its styles.
foreach ( $blocks as $block ) {
// Replace slash with hyphen for filename.
$slug = str_replace( '/', '-', $block );
wp_enqueue_block_style( $block, array(
'handle' => "themeslug-block-{$slug}",
'src' => get_theme_file_uri( "assets/blocks/{$slug}.css" ),
'path' => get_theme_file_path( "assets/blocks/{$slug}.css" )
) );
}
}
All of this is in active development!
Please note that the WordPress Style Engine is still pretty new, and the work for it is ongoing. The Style Engine document lists some of the planned upcoming work:
- Consolidate global and block style rendering and enqueuing (ongoing)
- Explore pre-render CSS rule processing with the intention of reduplicating other common and/or repetitive block styles. (ongoing)
- Extend the scope of semantic class names and/or design token expressions, and encapsulate rules into stable utility classes.
- Propose a way to control hierarchy and specificity, and make the style hierarchy cascade accessible and predictable. This might include preparing for CSS cascade layers until they become more widely supported, and allowing for opt-in support in Gutenberg via
theme.json
. - Refactor all blocks to consistently use the “style” attribute for all customizations, that is, deprecate preset-specific attributes such as
attributes.fontSize
.
You can track the development status on the GitHub project board.
Even with these limitations, this tweet from Rich Tabor and the following video demonstrates the unlimited opportunities we have for customizing the appearance of a block theme — without even touching code!
⚠️ Contains auto-playing media
All made possible, thanks to the WordPress Style Engine and its JSON parsing superpowers.
Additional resources
We covered a lot of ground in this article! I thought I would share some of the resources I relied on for the information.
Documentation
- Block styles generation (Style Engine) (WordPress 6.1 Field Guide)
- Core Styles and Theme Customization: the next steps (Make WordPress Core)
Tutorials
- Standardized Design Tokens and CSS for a consistent, customizable, and interoperable WordPress future (MR Web Design)
- An Overview of Layout-Related Classes (Gutenberg Times)
GitHub issues
A lot of the context for this article comes from proposals and issues reported to the WordPress GitHub repo.
- The Block – Theme contract (#35470)
- General CSS Concepts: Declaring style vs. describing state (#38694)
- Explore options to add back semantic classnames to block wrappers (#38719)
- Proposal: Standardized block markup, theme.json design tokens, and CSS classes to improve interoperability (#38998)
- Layout: Use semantic classnames, centralize layout definitions, reduce duplication, and fix blockGap in theme.json (#40875)
- Global Styles Ongoing Roadmap (#41232)
- Heading block- Add a wp-block-heading CSS class (#42122)
Next up…
We’re actually all done! But I’ve created a page that pulls all of what we learned about CSS in WordPress Block Themes and provides you with a one-stop place you can reference anytime you need it.