Global styles: render element styles set only inside a breakpoint - #81265
Conversation
`get_block_nodes()` looked for a block's elements under `styles.blocks.<block>.elements` and emitted the breakpoint nodes from inside that loop. An element styled only inside `@mobile` or `@tablet` was never looped over, so no node was created and no CSS came out. The same element styled outside the breakpoint as well worked fine, which made the behaviour look like it depended on the theme rather than on a missing loop. Sanitization already keeps these styles (`class-wp-theme-json-gutenberg.php:1320`), and the element pseudo handling a few lines below already accounts for a state that only exists in a breakpoint, so this was an oversight rather than a decision. Collect element names from the block node and from each breakpoint before looping, and only emit the default node when the element is styled outside a breakpoint. The documented behaviour was already unconditional: "Any style property that is valid at the block or element level can be nested under one of these keys." The responsive styles guide gains an example of the breakpoint-only case, and its CSS sample is corrected to the selector WordPress actually outputs.
| @media (width <= 480px) { :root :where(.wp-block-group a) { color: red; } } | ||
| :root :where(.wp-block-group a:hover) { color: navy; } | ||
| @media (width <= 480px) { :root :where(.wp-block-group a:hover) { color: darkred; } } | ||
| :root :where(.wp-block-group a:where(:not(.wp-element-button))) { color: blue; } |
There was a problem hiding this comment.
This reflects the actual output.
There was a problem hiding this comment.
Pull request overview
Ensures breakpoint-only block element styles generate responsive CSS.
Changes:
- Collects element names across default and responsive block styles.
- Adds PHPUnit coverage for breakpoint-only elements and pseudo-states.
- Documents breakpoint-only styling and correct selectors.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
lib/class-wp-theme-json-gutenberg.php |
Emits nodes for elements styled only within breakpoints. |
phpunit/class-wp-theme-json-test.php |
Tests responsive element and pseudo-style output. |
docs/how-to-guides/themes/global-settings-and-styles.md |
Adds examples and corrects generated selectors. |
There was a problem hiding this comment.
Looks good.
I've made a PR #81291, which is quite closely related to this one (and based on your branch, as I think I needed the changes from it to avoid a conflict). I'm not sure if we want to role them into one fix.
I'll make the backports for that PR, but if you want to take it over and use it for this too, then feel free. I'll be AFK until next Tuesday so won't be able to continue with it tomorrow or Monday.
edit: actually, I'll just go ahead and make a backport for both branches.
|
Yeah, sorry, I could've probably done this more cleanly. Maybe I made more work. I made a core backport PR for this PR and my PR (#81291) here - WordPress/wordpress-develop#12918. If we want the changes in 7.1, then I think we also need a PR for the 7.1 branch. |
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
I believe the release branch hasn't been created yet, but better double check Edit: it's going to be created next week as per https://make.wordpress.org/core/2026/08/05/wordpress-7-1-release-candidate-phase/ |
Oh, let's go with your PR. I created one (part of a local automation) but easy to close. |
Core PR 12918 already covers this change, so use that number instead of opening a second one.
tellthemachines
left a comment
There was a problem hiding this comment.
This is working well and changes LGTM!
…1265) Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: talldan <talldanwp@git.wordpress.org> Co-authored-by: tellthemachines <isabel_brison@git.wordpress.org>
|
I just cherry-picked this PR to the wp/7.1 branch to get it included in the next release: d261fb3 |
|
Just noticed we need equivalent editor changes, putting up a PR for that now Edit: #81307 |
What?
Blocks #81253. Raised by review on that PR.
Element styles written inside
@mobileor@tabletnow produce CSS whether or not the same element is also styled outside the breakpoint.Why?
get_block_nodes()looks for a block's elements understyles.blocks.<block>.elementsand emits the breakpoint nodes from inside that loop (lib/class-wp-theme-json-gutenberg.php:3706-3733). An element styled only inside a breakpoint is never looped over, so no node is created and no CSS is output. Add a link colour outside the breakpoint and the same theme.json starts working, which makes it look theme-dependent rather than like a missing loop.Sanitization already keeps these styles (
:1320), and the element pseudo handling just below already accounts for a state that exists only in a breakpoint, so this reads as an oversight.docs/how-to-guides/themes/global-settings-and-styles.mdalready promises the unconditional behaviour: "Any style property that is valid at the block or element level can be nested under one of these keys."How?
Collect element names from the block node and from each breakpoint before looping. Only emit the default node when the element is styled outside a breakpoint.
Docs gain an example of the breakpoint-only case, and the existing CSS sample is corrected to the selector WordPress actually outputs (
.wp-block-group a:where(:not(.wp-element-button)), not.wp-block-group a).Testing Instructions
Three new cases in
phpunit/class-wp-theme-json-test.php. All three fail on trunk and pass here.Or in a theme's
theme.json:{ "version": 3, "styles": { "blocks": { "core/group": { "@mobile": { "elements": { "link": { "color": { "text": "red" } } } } } } } }Before: no CSS at all. After:
Add
"elements": { "link": { "color": { "text": "blue" } } }next to@mobileand both rules appear, on trunk and here, which is the behaviour that masked this.Not included
Three other paths accept styles that never render, all unrelated to breakpoints or fixed here:
styles.blocks.<block>.elements.<el>["@mobile"],styles.elements.<el>["@mobile"], andstyles.blocks.<block>.variations.<v>.elements/.blocks. Worth separate issues.