A while ago Ganesh Dahal wrote a post here on CSS-Tricks in response to a tweet asking about adding CSS box shadows on WordPress blocks and elements. There’s a lot of great stuff in there that takes advantage of new features that were delivered in WordPress 6.1 that provide control to apply shadows to things directly in the Block Editor and Site Editor UI.
Ganesh briefly touched on button elements in that post. I’d like to pick it up and go deeper into approaches to styling buttons in WordPress block themes. Specifically, we will open a fresh one theme.json
archive and break down different approaches to styling buttons in the schema.
Why buttons, you ask? That’s a good question, so let’s start with that.
The different types of buttons
When we talk about buttons in connection with the WordPress Block Editor, we have to distinguish between two different types:
- Child blocks inside the Buttons block
- Buttons that are embedded in another block (eg the post comment form block)
If we add both of these blocks to a template, they will have the same appearance by default.

But the marking 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. These are the regular classes – .wp-block-button
and .wp-element-button
— which ensures uniform style between the two buttons.
If we were writing CSS, we would target these two classes. But as we know WordPress block themes have another way to manage styles and that is through theme.json
file. Ganesh also covered this in great detail and you would do well to read his article.
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 schemas written in property:value pairs. The top level properties are called “sections” and we will work with them styles
section. This is where all the styling instructions go.
We will focus specifically on elements
in styles
. This selector targets HTML elements that are shared between blocks. This is the basic shell we work with:
// theme.json
{
"version": 2,
"styles": {
"elements": {
// etc.
}
}
}
So what we need to do is define one button
element.
={
"version": 2,
"styles": {
"elements": {
"button": {
// etc.
}
}
}
}
To button
corresponds to HTML elements used to mark front-end button elements. These buttons contain HTML tags that can be one of our two button types: a standalone component (ie the Button block) or a component nested within another block (ie the Post Comment block).
Instead of 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 is one color
object in there which in turn supports background
and text
properties where we specify the values we want:
{
"version": 2,
"styles": {
"elements": {
"button": {
"color": {
"background": "#17a2b8",
"text": "#ffffff"
}
}
}
}
}
This changes the color of both button types:

If you open DevTools and look at the CSS that WordPress generates for the buttons, we see that .wp-element-button
class adds the styles we defined in theme.json
:
.wp-element-button {
background-color: #17a2b8;
color: #ffffff;
}
These are our standard colors! Next, we’ll give users visual feedback when they interact with the button.
Implementing interactive button styles
Since this is a site all about CSS, I bet many of you are already familiar with the interactive states of links and buttons. We can :hover
mouse cursor over them, tab them in :focus
click on them to make them :active
. Heck, there’s even one :visited
state to give users a visual indication that they have clicked this before.
These are CSS pseudo-classes and we use them to target a link’s or button’s interactions.
In CSS we can style one :hover
state like this:
a:hover {
/* Styles */
}
IN theme.json
we will 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 basically follow an overview:
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?
Styling buttons nested in individual blocks
Let’s imagine that we want all buttons to have our base styles, with one exception. We want the Submit button in the Submit 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, input, instructional text, and the button. To target the button in this block, we need to follow the same kind of JSON structure that we did for button
element, but applied to the Post Comment Form block it is attached to core/post-comments-form
element:
{
"version": 2,
"styles": {
"elements" {
"button": {
// Default button styles
}
}
"blocks": {
"core/post-comments-form": {
// etc.
}
}
}
}
Please note that we no longer work in elements
further. Instead, we work inside blocks
which is reserved for configuring actual blocks. Buttons, on the other hand, are considered a global element since they can be nested in blocks, although they are also available as a stand-alone block.
The JSON structure supports elements within elements. So if there is one button
element in the Post Comment Form block, we can target it in 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 we’re not just targeting a specific block – we’re targeting a specific element contained within that block. Now we have a standard set of button styles that apply to all buttons in the theme, and a set of styles that apply to specific buttons 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 Submit Comment Form button? It’s the same deal as the way we did it for the default styles, only they’re defined inside 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.
}
}
}
}
}
}
What about buttons that aren’t in blocks?
WordPress automatically generates and applies the correct classes to print these button styles. But what if you’re using a “hybrid” WordPress theme that supports blocks and site-wide editing, but also includes “classic” PHP templates? Or what if you made a custom block, or even have a legacy shortcode that includes buttons? None of these are handled by the WordPress Style Engine!
No problems. In all these cases you would add .wp-element-button
class in the template, block, or shortcode markup. The styles generated by WordPress will then be applied in these cases.
And there may be some situations where you have no control over the selection. For example, some block plugins can be a bit too opinionated and liberally apply their own style. This is where you can typically go to the “Advanced” option in the block’s options panel and apply the class there:

Concludes
While typing “CSS” in the theme.json
may feel awkward at first, I’ve found 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 benefits of using theme.json
:
- The styles are applied to buttons in both the frontend view and the block editor.
- Your CSS will be compatible with future WordPress updates.
- The generated styles work with both block themes and classic themes – there’s no need to duplicate anything in a separate style sheet.
If you have used theme.json
styles in your projects, so please share your experiences and thoughts. I look forward to reading any comments and feedback!