WordPress.org

WordPress Developer Blog

You don’t need theme.json for block theme styles

You don’t need theme.json for block theme styles

If you’re an old-school WordPress theme author or someone who’s intimately familiar with CSS, styling block themes can sometimes feel limiting. This is especially true if you’re trying to stick to the standard theme.json system. Or maybe you just don’t like using the JSON format to handle design. The truth is that there are many reasons you might rather stick to good ol’, tried-and-true CSS.

Opting out of JSON in favor of plain CSS is entirely possible with block themes. And perhaps it’s even the correct path, depending on the parameters of your project.

In this post, you will learn how to style block themes with custom CSS using the most up-to-date techniques.

You should embrace theme.json

In Mastering theme.json: You might not need CSS, I broke down all the reasons that you should use the theme.json system in WordPress. 

Honestly, I still stand 100% by the content of that post. theme.json is the recommended way of making style changes for themes, and it will only become more powerful over time, integrating more and more into the editing experience.

It’s important to note that the WordPress theme.json system is a standardized method of defining both settings and styles in a format (JSON) that can be accessed from both PHP and JavaScript. There are numerous benefits to such standards. For example, you get automatic integration with the Styles panel in the Site Editor. This lets your theme users view and customize those styles through the user interface.

I won’t rehash all of the advantages outlined in that article. But I do recommend reading it for a full understanding of what’s possible.

For this article, I will assume that you completely understand the power of theme.json but the benefits of using it don’t outweigh the disadvantages for your use case. Maybe you’re building a bespoke site where the client will never interact with the Styles panel or need to manipulate the design in any way. You can still take advantage of the many Block Editor features while still writing all your own CSS.

The theme.json structure

Before diving into how to handle block theme styles without JSON, it’s important to break down the various items that you can set via theme.json, which includes things like the JSON schema, settings, styles, and more.

Here is a look at the top-level properties that you can define in your theme.json file:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 3,
	"settings": {},
	"styles": {},
	"customTemplates": {},
	"templateParts": {},
	"patterns": []
}

This article is only focused on opting out of using the styles property, which offers support for a standardized subset of CSS. Most of the other items will almost always make sense to define via JSON, an ideal format for settings and other types of project configuration.

Organizing your stylesheet system

Throughout most of WordPress theming history, theme authors dropped most or even all of their CSS into their theme’s style.css file. This method worked well in the earlier days of web development when most stylesheets were relatively small in comparison to today. Eventually, developers created their own systems for including stylesheets. But the standard set by WordPress was to load a stylesheet globally (across the site).

When the Block Editor and, later, block themes were introduced, WordPress moved toward a standard of only loading CSS on the front end when it’s actually needed for the current page being viewed. And while this is primarily handled through the theme.json system, WordPress also includes features for doing this with custom stylesheets.

In this section, let’s break down the best method of following WordPress’s standard of only including CSS when necessary while using custom stylesheets. 

Global stylesheet

The theme’s style.css file is still an important part of theming. You can use it to globally load styles for both the front end and editor. 

Unlike in the past, I no longer recommend adding all of your CSS to the style.css file. Instead, use it only for CSS that should be available globally on the front end and in the editor. In particular, leave your custom block styling out since not every block is loaded on every page.

To load your theme’s style.css on the front end, add this code to your theme’s functions.php:

add_action( 'wp_enqueue_scripts', 'themeslug_enqueue_styles' );

function themeslug_enqueue_styles() {
	wp_enqueue_style( 'themeslug-style', get_stylesheet_uri() );
}

To also load it in the editor, add this code:

add_action( 'after_setup_theme', 'themeslug_add_editor_styles' );

function themeslug_add_editor_styles() {
	add_editor_style( get_stylesheet_uri() );
}

For more information on how style.css works, check out the Main Stylesheet documentation in the Theme Handbook.

Block stylesheets

Block stylesheets open a lot of flexibility for styling blocks, freeing you up to use any CSS you need for individual blocks. Often, these are mentioned alongside the concept of Block Style Variations, but that is not the only use case for them. You can use them for simply styling the default design for any block.

Suppose you wanted to add custom styles for the Image block. You’d first need to create a CSS file for this. For this example, create a core-image.css file in a directory named /assets/blocks in your theme.

To load this file only when the Image block is in use, you must use the wp_enqueue_block_style() function, as shown in this code:

add_action( 'init', 'themeslug_enqueue_block_styles' );

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

One best practice when styling blocks is to wrap the selector in :root :where() in your stylesheet. This will lower the specificity, matching it to most Core block styles and allowing users to override via the Styles interface in the admin.

For example, if you wanted to give <img> elements within the Image block a 2px purple border, the code in your core-image.css file would look like this:

:root :where(.wp-block-image img) {
	border: 2px solid #a78bfa;
}

The practice of using :root :where() may fall short at times when you need to overrule a more specific CSS rule, but it is a good starting point for most cases.

Combining WordPress and custom theme.json presets with stylesheets

Even if you don’t want to write style rules in JSON and prefer the stylesheet route, you may consider at least using presets. Presets are actually nothing more than CSS custom properties that WordPress generates. They also give your users options to choose from via the inspector controls for individual blocks.

Here’s an example of offering a basic palette as color presets:

{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 3,
	"settings": {
		"color": {
			"palette": [
				{
					"color": "#ffffff",
					"name": "Base",
					"slug": "base"
				},
				{
					"color": "#000000",
					"name": "Contrast",
					"slug": "contrast"
				},
				{
					"color": "#89cff0",
					"name": "Primary",
					"slug": "primary"
				}
			]
		}
	}
}

WordPress auto-generates CSS custom properties from these presets, making them globally available to use in both your theme.json or your own stylesheets:

:root {
	--wp--preset--color--base: #ffffff;
	--wp--preset--color--contrast: #000000;
	--wp--preset--color--primary: #89cff0;
}

Let’s suppose that you wanted to assign those colors to the following:

  • Base: Site body background
  • Contrast: Site text color
  • Primary: Link color

Generally, you would use the styles section in theme.json to define this:

{
	"styles": {
		"color": {
			"background": "var(--wp--preset--color--base)",
			"text": "var(--wp--preset--color--contrast)"
		},
		"elements": {
			"link": {
				"color": {
					"text": "var(--wp--preset--color--primary)"
				}
			}
		}
	}
}

But there’s no rule that says you must do this. Again, you can skip using styles in theme.json altogether.

Dropping this your theme’s style.css file would have the same effect:

body {
	background-color: var(--wp--preset--color--base);
	color: var(--wp--preset--color--contrast);
}

a:where(:not(.wp-element-button)) {
	color: var(--wp--preset--color--primary);
}

It’s not one or the other

While I’m an advocate of embracing theme.json as much as possible, I also use custom stylesheets when Core doesn’t quite support what I need. And it’s perfectly OK to use both methods in tandem.

If you’re just more comfortable with CSS, I see the desire to stick with it. And I won’t stand in your way if you prefer stylesheets.

I also won’t recommend ditching theme.json in its entirety, but you can absolutely do it while building a block theme. Or even start with stylesheets and integrate more into theme.json as you learn.

Props to @areziaal and @bph for feedback and review on this post.

Categories: ,

2 responses to “You don’t need theme.json for block theme styles”

  1. Lovro Hrust Avatar

    I usually use both theme.json and separate css styles (compiled from scss). When there is too much style rules I move them to a separate scss file, and they are usually related to block and enqueued by wp_enqueue_block_style. But, there are some styles which are more related to specific page and those for optimization sake I load only if specific page is present (it is quite tricky to achieve that for normal wp admin page editor and site editor, I could open source my code for this. I also rely extensively on block style variations, whom is now pretty easy to define, they have very similar structure to theme.json and you put them in styles folder. There is a neat trick that I discovered – even if you move all css from style json, you can still get style registered by json file if you put “css”: “” in “styles” section.

  2. Weston Ruter Avatar

    Don’t forget to add the path data for the theme stylesheet so it can be inclined!

    See core ticket: https://core.trac.wordpress.org/ticket/63007

    Plugin that implements this for Twenty Twenty-Five: https://gist.github.com/westonruter/09e553a7b66d1a2e68cd5a9ed351c59b

Leave a Reply

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