How the Style Engine Generates Classes

Avatar of Ganesh Dahal
Ganesh Dahal on (Updated on )

The WordPress Style Engine generates the CSS for a block theme. Why would you want to know how an invisible process like that works? Well, just like writing CSS, you will want to ensure your code is organized and structured so that your styles properly use the CSS Cascade.

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.

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 the style.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.

WordPress Site Editor screen with the Global Styles settings open and highlighted with a red border.

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.

WordPress Site Editor screen with the Global Styles settings open to color options.

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.

Processing theme.json

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.

The WordPress Site Editor open on the Homepage template and displaying a large bright orange box with Hello World in it in black.

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.

BlockSemantic ClassStateful 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 and wideWidth settings (either in theme.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.
The block editor with a two-column layout and the Layout settings open.

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.

Source: Make WordPress Core

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
Cycling through various theme styles.

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

Tutorials

GitHub issues

A lot of the context for this article comes from proposals and issues reported to the WordPress GitHub repo.

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.