WordPress 7.0 is finally bringing something we’ve been hacking around for years: native WordPress theme.json pseudo-selectors for individual blocks. I honestly thought I’d seen every possible “workaround” for styling hover states in block themes. From enqueuing dedicated CSS files just for a single button variation to using add_inline_style hacks that made the source code look like a crime scene—we’ve done it all.
Previously, pseudo-class support in theme.json was gatekept. You could only use :hover or :focus on global HTML elements like buttons and links under the styles.elements key. If you wanted a specific block variation—say, an “Outline” button—to change text color on hover, you were forced back into the world of manual CSS. However, this update changes the game by allowing these selectors directly on blocks and their variations.
The Old Way: The “Hack” Approach
Before this update, if you wanted a specific button variation to have a hover state, you probably did something like this in your theme’s CSS file:
/* This was the only way to target block variations reliably */
.wp-block-button.is-style-outline .wp-block-button__link:hover {
background-color: var(--wp--preset--color--primary);
color: #ffffff;
}
It worked, but it broke the declarative nature of block themes. Furthermore, it made maintenance a bottleneck because your styles were split between JSON and CSS files.
Using WordPress theme.json pseudo-selectors in 7.0
Specifically, WordPress 7.0 adds support for :hover, :focus, :focus-visible, and :active. You can now define these directly within the block’s style configuration. Therefore, your theme.json stays as the single source of truth.
Here is how you would handle an outline button variation hover state natively:
{
"styles": {
"blocks": {
"core/button": {
"variations": {
"outline": {
"color": {
"background": "transparent",
"text": "currentColor"
},
":hover": {
"color": {
"background": "currentColor",
"text": "white"
}
}
}
}
}
}
}
}
Important “Gotchas” to Remember
While this is a massive win, there are a few things to keep in mind to avoid a debug session from hell:
- JSON Only: There is currently no UI in Global Styles for these states. You have to hand-code this in
theme.json. - Strict Support: Only the four mentioned selectors are supported for
core/button. If you try to sneak in:visited, it will be ignored. - Independence: Styles defined at the block level and variation level are independent. They don’t conflict, allowing for highly granular control.
I’ve written before about styling forms with theme.json, and this pseudo-selector support feels like the logical next step in making WordPress truly declarative. If you’re still wrestling with complex modern CSS features, this update simplifies your workflow significantly.
Look, if this WordPress theme.json pseudo-selectors stuff is eating up your dev hours, let me handle it. I’ve been wrestling with WordPress since the 4.x days.
Refactoring for Stability
Switching your interactive states to theme.json isn’t just about following the “new way.” It’s about reducing the CSS payload and ensuring your theme is future-proof as the Site Editor evolves. Consequently, you’ll spend less time debugging specificity issues caused by external stylesheets.