A little while back, Ganesh Dahal penned a post here on CSS-Tricks responding to a tweet that asked about adding CSS box shadows on WordPress blocks and elements. There’s a lot of great stuff in there that leverages new features that shipped in WordPress 6.1 that provide controls for applying shadows to things directly in the Block Editor and Site Editor UI.
Ganesh touched briefly on button elements in that post. I want to pick that up and go deeper into approaches for styling buttons in WordPress block themes. Specifically, we’re going to crack open a fresh theme.json
file and break down various approaches to styling buttons in the schema.
Why buttons, you ask? That’s a good question, so let’s start with that.
Table of Contents
When we’re talking about buttons in the context of the WordPress Block Editor, we have to distinguish between two different types:
- Child blocks inside of the Buttons block
- Buttons that are nested inside other block (e.g. the Post Comments Form block)
If we add both of these blocks to a template, they have the same look by default.
But the markup is very different:
<div class="wp-block-button"> <a class="wp-block-button__link wp-element-button">Button 1</a>
</div>
<p class="form-submit wp-block-button"> <input name="submit" type="submit" id="submit" class="wp-block-button__link wp-element-button" value="Post Comment"> </p>
As we can see, the HTML tag names are different. It’s the common classes — .wp-block-button
and .wp-element-button
— that ensure consistent styling between the two buttons.
If we were writing CSS, we would target these two classes. But as we know, WordPress block themes have a different way of managing styles, and that’s through the theme.json
file. Ganesh also covered this in great detail, and you’d do well giving his article a read.
So, how do we define button styles in theme.json
without writing actual CSS? Let’s do it together.
Creating the base styles
theme.json
is a structured set of schema written in property:value pairs. The top level properties are called “sections”, and we’re going to work with the styles
section. This is where all the styling instructions go.
We’ll focus specifically on the elements
in the styles
. This selector targets HTML elements that are shared between blocks. This is the basic shell we’re working with:
// theme.json
{ "version": 2, "styles": { "elements": { // etc. } }
}
So what we need to do is define a button
element.
={ "version": 2, "styles": { "elements": { "button": { // etc. } } }
}
That button
corresponds to HTML elements that are used to mark up button elements on the front end. These buttons contain HTML tags that could be either of our two button types: a standalone component (i.e. the Button block) or a component nested within another block (e.g. the Post Comment block).
Rather than having to style each individual block, we create shared styles. Let’s go ahead and change the default background and text color for both types of buttons in our theme. There’s a color
object in there that, in turn, supports background
and text
properties where we set the values we want:
{ "version": 2, "styles": { "elements": { "button": { "color": { "background": "#17a2b8", "text": "#ffffff" } } } }
}
This changes the color of both button types:
If crack open DevTools and have a look at the CSS that WordPress generates for the buttons, we see that the .wp-element-button
class adds the styles we defined in theme.json
:
.wp-element-button { background-color: #17a2b8; color: #ffffff;
}
Those are our default colors! Next, we want to give users visual feedback when they interact with the button.
Since this is a site all about CSS, I’d bet many of you are already familiar with the interactive states of links and buttons. We can :hover
the mouse cursor over them, tab them into :focus
, click on them to make them :active
. Heck, there’s even a :visited
state to give users a visual indication that they’ve clicked this before.
Those are CSS pseudo-classes and we use them to target a link’s or button’s interactions.
In CSS, we might style a :hover
state like this:
a:hover { /* Styles */
}
In theme.json
, we’re going to extend our existing button declaration with these pseudo-classes.
{ "version": 2, "styles": { "elements": { "button": { "color": { "background": "#17a2b8", "text": "#ffffff" } ":hover": { "color": { "background": "#138496" } }, ":focus": { "color": { "background": "#138496" } }, ":active": { "color": { "background": "#138496" } } } } }
}
Notice the “structured” nature of this. We’re basically following an outline:
We now have a complete definition of our button’s default and interactive styles. But what if we want to style certain buttons that are nested in other blocks?
Let’s imagine that we want all buttons to have our base styles, with one exception. We want the submit button of the Post Comment Form block to be blue. How would we achieve that?
This block is more complex than the Button block because it has more moving parts: the form, inputs, instructive text, and the button. In order to target the button in this block, we have to follow the same sort of JSON structure we did for the button
element, but applied to the Post Comment Form block, which is mapped to the core/post-comments-form
element:
{ "version": 2, "styles": { "elements" { "button": { // Default button styles } } "blocks": { "core/post-comments-form": { // etc. } } }
}
Notice that we’re no longer working in elements
anymore. Instead, we’re working inside blocks
which is reserved for configuring actual blocks. Buttons, by contrast, are considered a global element since they can be nested in blocks, even though they are available as a standalone block too.
The JSON structure supports elements within elements. So, if there’s a button
element in the Post Comment Form block, we can target it in the core/post-comments-form
block:
{ "version": 2, "styles": { "elements" { "button": { // Default button styles } } "blocks": { "core/post-comments-form": { "elements": { "button": { "color": { "background": "#007bff" } } } } } }
}
This selector means that not only are we targeting a specific block — we’re targeting a specific element that is contained in that block. Now we have a default set of button styles that are applied to all buttons in the theme, and a set of styles that apply to specific buttons that are contained in the Post Comment Form block.
The CSS generated by WordPress has a more precise selector as a result:
.wp-block-post-comments-form .wp-element-button,
.wp-block-post-comments-form .wp-block-button__link { background-color: #007bff;
}
And what if we want to define different interactive styles for the Post Comment Form button? It’s the same deal as the way we did it for the default styles, only those are defined inside the core/post-comments-form
block:
{ "version": 2, "styles": { "elements" { "button": { // Default button styles } } "blocks": { "core/post-comments-form": { "elements": { "button": { "color": { "background": "#007bff" } ":hover": { "color": { "background": "#138496" } }, // etc. } } } } }
}
WordPress automagically generates and applies the right classes to output these button styles. But what if you use a “hybrid” WordPress theme that supports blocks and full-site editing, but also contains “classic” PHP templates? Or what if you made a custom block, or even have a legacy shortcode, that contains buttons? None of these are handled by the WordPress Style Engine!
No worries. In all of those cases, you would add the .wp-element-button
class in the template, block, or shortcode markup. The styles generated by WordPress will then be applied in those instances.
And there may be some situations where you have no control over the markup. For example, some block plugin might be a little too opinionated and liberally apply its own styling. That’s where you can typically go to the “Advanced” option in the block’s settings panel and apply the class there:
Wrapping up
While writing “CSS” in theme.json
might feel awkward at first, I’ve found that it becomes second nature. Like CSS, there are a limited number of properties that you can apply either broadly or very narrowly using the right selectors.
And let’s not forget the three main advantages of using theme.json
:
- The styles are applied to buttons in both the front-end view and the block editor.
- Your CSS will be compatible with future WordPress updates.
- The generated styles work with block themes and classic themes alike — there’s no need to duplicate anything in a separate stylesheet.
If you have used theme.json
styles in your projects, please share your experiences and thoughts. I look forward to reading any comments and feedback!