Styling Buttons in WordPress Block Themes | CSS-Tricks

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:

  1. Child blocks inside the Buttons block
  2. 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.

A black button above a comment form that also contains a black button.

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:

A light blue button above a comment form that also contains a light blue button.

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 :focusclick 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.jsonwe 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.

A light blue button above a comment form that contains a light blue button.

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:

A WordPress block settings panel with the advanced settings expanded, highlighting the CSS classes section in red.

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:

  1. The styles are applied to buttons in both the frontend view and the block editor.
  2. Your CSS will be compatible with future WordPress updates.
  3. 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!

William

Leave a Reply

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