WordPress 6.2 added support for per-block CSS in theme.json
. That means now you can add in snippets of custom code when the built-in design tools don’t quite cover your use case.
theme.json
supports enough CSS features to cover most common styles. But it cannot reasonably cover every scenario and still have a matching design tool in the editor UI, which is an important consideration. If the interface covered every possibility, the user experience would suffer.
There are times when you still need good ol’ CSS for block themes.
Why not just stick CSS in style.css
and be done with it? Why bother mixing CSS into theme.json
?
The answers to those questions are up to you—you’re the theme designer. But the primary advantage of using theme.json
over a stylesheet is that the .json
file plugs the CSS back into the UI. And users can customize the code directly in the block’s custom CSS panel.
Modern WordPress lets themes and users communicate. The theme serves up a configuration via theme.json
, and users can customize it in the interface. Under the hood, it’s all formatted into JSON—even the user customizations—and works under a standard set of rules. The more you buy into this system as a designer, the more flexibility your users will have.
There are a few pitfalls too, and you will learn about those. The system isn’t perfect, but the tools are solid enough that you can start digging into this feature and deciding when and where to use it.
Adding per-block CSS
You should already be familiar with how to customize block styles in theme.json
. If not, get up to speed with the global settings and styles documentation.
You add custom styles by adding a new css
property to a styles.blocks.[block-name]
object in theme.json
. And here’s something cool: when you add CSS in this way, you do not need to know the selector. WordPress will automatically generate that for the block.
Notice how this JSON code formats the css
property:
{
"css": "color: red;"
}
The value is a CSS declaration that probably looks familiar. The only difference is that the JSON wraps the declaration in quotes and assigns it to the css
property.
Now, try your hand at adding a single CSS declaration. This theme.json
code example adds a custom letter-spacing
value to the Post Title block:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 2,
"styles": {
"blocks": {
"core/post-title": {
"css": "letter-spacing: 1px;"
}
}
}
}
Save those changes to your theme.json
. WordPress will generate the CSS for this on the front end and in the editor:
.wp-block-post-title {
letter-spacing: 1px;
}
Now, open the Appearance > Editor screen in the WordPress admin. Then, open the Styles panel and select Blocks > Post Title > Additional block CSS. Your custom CSS should appear, as you see below:
Because you registered the CSS in theme.json
, your users can make changes to it directly, right in the interface. They can skip the complexities of CSS specificity, and they’ll never know the hassle of trying to find the proper selectors with this example.
That’s a pretty powerful bridge between you and your users.
At the moment, WordPress only lets you add CSS to blocks in theme.json
. There is an open ticket to add element support.
Tips, tricks, and pitfalls
You have learned how to add basic CSS declarations, but there are also a few tricks you can use to handle more complex scenarios.
Using the & selector and brackets
WordPress supports the &
selector, much as you would see in a language like Sass. But it is not exactly the same. For example, it doesn’t support nested CSS blocks.
But it does work for appending selectors to the WordPress-generated block class.
A minute ago, you targeted the Post Title block, which has a class of .wp-block-post-title
. What if you wanted to target that only when it has a custom class attached to it? As, maybe, when you’re registering a custom block style variation?
Suppose you registered a block style named letter-spacing-sm
, which would have a generated class of .is-style-letter-spacing-sm
. You could append that class with &.is-style-letter-spacing-sm
and wrap your CSS declaration with brackets:
{
"core/post-title": {
"css": "&.is-style-letter-spacing-sm { letter-spacing: 1px; }"
}
}
WordPress will generate this CSS:
.wp-block-post-title.is-style-letter-spacing-sm {
letter-spacing: 1px;
}
Technically, you could write your custom code without the ampersand, and WordPress will append it correctly. But the &
gives you a visual reminder that .is-style-letter-spacing-sm
is being appended to another class.
Targeting a nested element
Where the ampersand really shines is with targeting nested elements. For example, suppose you wanted to rotate the text of Image block captions, as below:
If you wrote the code below, you would end up with a broken selector:
{
"core/image": {
"css": "figcaption { transform: rotate( 1deg ); }"
}
}
WordPress would append figcaption
to the block class name:
.wp-block-imagefigcaption {
transform: rotate( 1deg );
}
You either have to put a space at the beginning of your code or use &
. Remembering to add a space is not ideal, and it would be easier to break when users make edits.
It’s a lot easier and less prone to errors to write it like so:
{
"core/image": {
"css": "& figcaption { transform: rotate( 1deg ); }"
}
}
Which will trigger WordPress to generate the expected CSS code block:
.wp-block-image figcaption {
transform: rotate( 1deg );
}
Large CSS code blocks
If you’ve worked with JSON enough in the past, you’ve likely already noticed a couple of major drawbacks to saving CSS as JSON string values:
- There’s no built-in syntax highlighting for the CSS.
- JSON doesn’t support line breaks, so forget about using multi-line CSS blocks.
There might be some tooling to help you work around those limitations, but you will quickly run into issues when using the default system.
For example, you might want add a border that looks hand-drawn to the <img>
element output by the Image block, as you see below:
This design takes several CSS declarations to pull off:
{
"core/image": {
"css": "& img { border: 2px solid currentColor; overflow: hidden; box-shadow: 0 4px 10px 0 rgba( 0, 0, 0, 0.3 ); border-radius: 255px 15px 225px 15px/15px 225px 15px 255px !important; }"
}
}
And it’s the moment that you might start questioning whether using this feature is worth it, since JSON is not the ideal method for writing big blocks of CSS code. Add more than a few CSS declarations, and things get really messy, really fast.
The code also looks messy in the editor—hard for your users to read and a little scary for some to edit. One way around this: use \n
for line breaks and \t
for tabs in the final string. Here’s an example:
{
"core/image": {
"css": "& img {\n\tborder: 2px solid currentColor;\n\toverflow: hidden;\n\tbox-shadow: 0 4px 10px 0 rgba( 0, 0, 0, 0.3 );\n\tborder-radius: 255px 15px 225px 15px/15px 225px 15px 255px !important;\n}"
}
}
That will give users a nicely-formatted CSS block in the interface:
& img {
border: 2px solid currentColor;
overflow: hidden;
box-shadow: 0 4px 10px 0 rgba( 0, 0, 0, 0.3 );
border-radius: 255px 15px 225px 15px/15px 225px 15px 255px !important;
}
The JSON might not look great to you, but your users will likely appreciate the CSS formatting.
Tip: use \"
to escape double-quotes in your CSS when used in a JSON string.
When to use CSS in theme.json
CSS support in theme.json
can be both a powerful feature and a recipe for headache. In its current state, it works well when you have small design changes for specific blocks, especially if your theme doesn’t load any stylesheets.
The integration with the Styles interface in the Site Editor is also great for users who want to customize these bits of CSS that your theme adds.
If you want to get the most out of it while also using bigger blocks of CSS, you want tools that soothe the pain points. For example, a script that pulls CSS from stylesheet files, formats the code as a valid JSON string, and saves it to theme.json
could be the answer. That’s outside the scope of this post, but maybe it’ll motivate you to build solutions that push the boundaries of what theming could be.
Ultimately, it’s another tool in the toolbox. Use it when it makes sense for your project.
Props to @marybaum, @bph, and @poena for feedback and review. Photo of palm trees by @shameemreza from the WordPress photos directory.
Leave a Reply