WordPress.org

WordPress Developer Blog

Leveraging theme.json and per-block styles for more performant themes

With each major WordPress update, theme authors gain access to new tools that decrease their development workload and can increase the performance of their themes. Most of these improvements in the last few releases come from theme.json updates.

The theme.json file adds extra settings and styles that can replace the need for custom CSS. These styles are inlined and output on an as-needed basis. Major WordPress updates have continually brought more control to theme authors through the JSON format.

Another area where the block system shines is its feature for loading per-block styles. When WordPress detects a specific block on a particular front-end view, it will inline that block’s CSS inside of the <head> element of the site. It does this for third-party blocks as well as those from core.

All in all, it is a highly efficient system. And, when an entire site is made of blocks, such as with block themes, WordPress can often effectively inline only the bits of CSS needed for any given front-end view.

The potential downside of inlining all CSS is that you lose out on browser caching. However, considering that many WordPress themes’ stylesheets have ballooned in size over the last decade, the block system is, in all likelihood, more efficient than the average classic theme. But the system can only be as good as its weakest component. This puts much of the responsibility on the shoulders of theme authors to take advantage of the tools available.

To help encourage best practices, let’s talk about the two primary tools for ensuring a performant theme: theme.json and per-block styles.

Utilizing theme.json styles

Building WordPress themes in the modern era requires developers to rethink the approaches they have relied on in the past. It calls for them to go “all in” on a foundation that is somewhat outside of their control, relying on WordPress to do much of the heavy lifting.

The primary role of the theme is much the same as it has always been. It is the design, the paint that coats the walls of the house. However, the underlying architecture is all built atop a standard system.

This comes with some advantages, such as allowing all extensions—both plugins and themes—to benefit from the same performance-related features.

In the past, the onramp to theme design was style.css, but this is no longer the case. Much (not all) of what theme authors created via their stylesheets has been incorporated into theme.json. This can be a bit off-putting for those who are comfortable writing CSS. That is understandable. However, taking a step outside of a comfort zone can sometimes mean walking into a new and wonderful world of possibilities.

The usage of JSON is a part of a framework that allows WordPress, themes, and users to communicate, working together to build the website. WordPress provides the interface along with default settings and styles. Themes use theme.json to overrule the defaults. And users can make their own customizations through the interface, which is essentially custom JSON that’s stored in the database.

Because this all exists within this standard framework, it means that every performance improvement in new versions of WordPress percolates throughout the entire ecosystem.

These benefits require buy-in from theme authors. To get the most from modern WordPress, developers must use theme.json to configure as much of the site’s visual appearance as possible before moving onto stylesheet-based solutions. It is the foundation of block theme development, but classic theme authors can also take advantage of it.

The theme.json page in the Theme Handbook is the best place to start learning how to work with theme.json. And, the Living Reference in the Block Editor Handbook maintains an up-to-date reference of settings and styles. In particular, theme authors should always check if there is a style option available before reaching for custom CSS.

Each custom CSS rule placed in theme.json also benefits from the same contextual, inline-style system that blocks use.

Per-block styles

theme.json is not a magic bullet, a cure-all for every problem that a theme author is trying to solve. It is a tool that should be used to its fullest extent possible. Not all options are currently accessible via JSON, and there are times when it makes sense to use a stylesheet. However, the long-term aim is to use as little custom CSS as possible.

Traditionally, whenever a theme author wanted to add a bit of design flair (or, just any CSS, really), they would add code to their theme’s style.css file. In the early days of WordPress, it was not uncommon for themes to have non-minified, uncompressed stylesheets in the sub-10 kb range. The web was slower and websites simpler. As websites grew in complexity, stylesheets followed in size.

The following are the last three default themes from the classic era and their style.css file sizes:

  • Twenty Twenty-One: 158 kb
  • Twenty Twenty: 125 kb
  • Twenty Nineteen: 228 kb

Compared to many third-party themes, all three have relatively simple designs. The growing complexity of websites and their stylesheet sizes are problems that the block system seeks to solve.

While future default WordPress themes will likely rely primarily on theme.json for their design needs, developers in the outside world will often find themselves reaching for style.css to make customizations that are not yet possible via theme.json.

That is where wp_enqueue_block_style() comes in. It allows theme authors to take advantage of the same inline-style system that WordPress and block plugins get for free. This function allows theme authors to break up their stylesheets for individual blocks, which are only added as an inline style when they are needed.

Let’s take a look at a practical example. Suppose you had some custom CSS you needed to apply to the core Group block. Assuming your file was located in your theme’s assets/blocks/core-group.css file, use the following code in the theme’s functions.php file to enqueue it:

add_action( 'init', 'themeslug_enqueue_block_styles' );

function themeslug_enqueue_block_styles() {
    wp_enqueue_block_style( 'core/group', array(
        'handle' => 'themeslug-block-group',
        'src'    => get_theme_file_uri( "assets/blocks/core-group.css" ),
        'path'   => get_theme_file_path( "assets/blocks/core-group.css" )
    ) );
}

Unlike related wp_enqueue_* functions, you must provide the filepath to the CSS file along with the URL path. This is because WordPress needs to get the file’s content and print it inside the <head> element.

The above was an overly simplistic example. It is more than likely that theme authors will need to load multiple files, and rewriting that same code for each stylesheet would become a management nightmare.

When enqueuing styles for multiple blocks, use an array and loop through them instead. The following code snippet assumes that you have stylesheets for the core Button, Heading, and Paragraph blocks:

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" )
        ) );
    }
}

You could get even fancier with that and use PHP’s glob() function to automatically generate the array of files. However, the goal with the above code is for you to see the mechanics of it at work.

For those wondering about the core- prefix for the filenames and the need for the str_replace() call, there is a good reason for this. It’s set up that way because it also assumes that you may want to support third-party plugins with block names like pluginslug/blockslug and filenames like pluginslug-blockslug.css. If you only plan to support core blocks in your theme, you can always simplify the code.


For many block themes, there will not be a need for a style.css file at all, outside of adding the file header information with the theme name and other details. When every element on the page is a block, there is hardly a need for much else. There are certainly scenarios that fall outside of this scope, but the bulk of most theme CSS can be handled via theme.json and per-block styles.

Props to @juanmaguitar, @annezazu, @mburridge, @webcommsat, and @bph for feedback and review.

11 responses to “Leveraging theme.json and per-block styles for more performant themes”

  1. vishalgaikwad Avatar

    This article helped to clear an air of confusion/doubts on how to build block themes correctly. Such articles from the dev team are welcome, as many times as a theme builder I get lost into so many things said & written about the block themes. It becomes even more challenging considering its a transitioning phase. Thank you Justin for such a helpful post!

  2. […] Now, you will create a block-specific stylesheet. WordPress will load its contents and inline the CSS only when the block is in use. For more information on per-block stylesheets, visit Leveraging theme.json and per-block styles for more performant themes. […]

  3. […] You could add the custom CSS to your theme’s primary style.css file. But, since you will probably tie this code to a specific block, you should register a custom block stylesheet, as described in Leveraging theme.json and per-block styles for more performant themes. […]

  4. abitofmind Avatar

    How do I conditionally enqueue Custom Block CSS for a 3rd party block?
    (Concretely: Pattern for filename and for inclusion in loop)

    I HAD THIS PROBLEM:

    Image Block has unexplainable bottom gap if wrapped within 3rd party container block (Gutenberg #53537)

    FOR WHICH THE CSS FIX WAS RATHER EASY:

    .wp-block-greenshift-blocks-container .wp-block-image { margin: 0; }

    CURRENTLY I HAVE THIS CSS IN THE CORE IMAGE BLOCK CSS FILE:

    wp-content/my-theme/assets/css/blocks/core-image.css

    for which the loop inclusion is: core/image

    MORE EFFICIENT TO CONDITIONAL ENQUEUE 3RD PARTY BLOCK
    In theory and also in practice if the CSS grows.
    Because my website contains far more pages that feature core image blocks but no Greenshift containers (=the rule) than pages who feature Greenshift containers wrapping native image blocks (=the exception).

    Could you tell me what the naming convention for the file and for the inclusion in the loop would be then? Here in this comment and ideally also amend the article with this use case. Thanks!

    FILE:
    ? wp-content/my-theme/assets/css/blocks/wp-block-greenshift-blocks-container.css

    LOOP:
    ? greenshift/container

    1. Justin Tadlock Avatar

      How do I conditionally enqueue Custom Block CSS for a 3rd party block?

      WordPress automatically conditionally loads per-block stylesheets. So, if you use the techniques laid out in this post, WordPress will only output the CSS if the block is used on the page.

      So, if you add a stylesheet specifically for greenshift-blocks-container, it will only load when that block is in use.

      Could you tell me what the naming convention for the file and for the inclusion in the loop would be then?

      It looks like the name of the block plugin you’re using has a namespace of greenshift-blocks and the slug for the container block is simply container. So I’d use greenshift-blocks/container in the array (assuming you’re referring to the last code example in the article) and name the CSS file greenshift-blocks-container.css.

      1. abitofmind Avatar

        The naming conventions as laid out in your comment worked perfectly!

        Could you please amend the article accordingly?

        • How do I find out the name space of another plugin and its particular bock?

        • How does the namespace work for core blocks both in referenceing and in their filenames.

        1. abitofmind Avatar

          Your article’s paragraph starting with “For those wondering about the core- prefix for the filenames and the need for the str_replace() call …” explains that both “Core” and “Your-Third-Party-Plugin-Slug” are both explicitly expressed both in CSS file names and the slugs in the array for the loop in wp_enqueue_block_styles() in functions.php

          What’s missing is the mention that in the CSS selector namespace these behaves slightly differently:

          ## 1st and 3rd party CSS selector start
          – Both start with `.wp-block-*`
          – This seems sort of the master prefix for all blocks to be used in WordPress block themes.

          ## 1st party CSS selector
          – e.g. `.wp-block-image`
          – Core blocks e.g. ìmage` start right with their block name (so the “core” prefix is only implicit for them).

          ## 3rd party CSS selector
          – e.g. `.wp-block-greenshift-blocks-container`
          – 3rd party blocks have the plugin slug explicitly, e.g. `greenshift-blocks` and are then followed by the specific block slug e.g. `container`.

          This is yet undocumented for newcomers.

  5. abitofmind Avatar

    The comments completely loose their linebreaks!

    • And I now include some bullets as separators.

    • Luckily I had my headline formatted as uppercase.

    • Which helps readability at least a bit when reading the comment as a a long continuous string without paragraphs or line breaks.

    1. abitofmind Avatar

      Line 1

      Line 3 after 2 was omited.
      Line 4

      Line 7 after 5-6 having been empty.

      Line 9 concludes this test.

  6. abitofmind Avatar

    The comments now preserve/render linebreaks just fine. Multiple empty lines are summed up as a single empty line. So far so normal in the HTML context, when not using `white-space: pre-wrap`.

    Next step: Normal block editor comments. As this is a dev blog, basic formatting with a basic block assortment as in the support forums would make sense here (quotes, codeblock, bulleted/numbered lists with indentation, images with linking.

Leave a Reply

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