Skip to content

States: Scope per-instance rules to the block's root element - #83986

Merged
aaronrobertshaw merged 3 commits into
trunkfrom
fix/states-selector-scoping
Oct 6, 2026
Merged

aaronrobertshaw merged 3 commits into
trunkfrom
fix/states-selector-scoping

Conversation

@aaronrobertshaw

@aaronrobertshaw aaronrobertshaw commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Fixes responsive and pseudo-state block values doing nothing on List Item, in the editor and on the front end.

What?

Scopes per-instance state rules to the element the instance class is on, when a block's selector starts with a different block's class.

Why?

List Item declares "root": ".wp-block-list > li". The instance class goes on the li, but the rule swapped the leading .wp-block-list for it. That gives .wp-states-xxxx > li, which asks for an li inside the list item and matches nothing.

So a List Item mobile value for colour, background, typography, spacing or border is inert today. Global Styles and the desktop block value already work. Found while adding background support to List Item.

How?

A leading compound is treated as an ancestor only when:

  • it is a wp-block-* class that is not this block's own, derived from the block name, and
  • the compound after it carries no wp-block-* class of its own.

Then the ancestor and its combinator are kept, and the next compound is swapped for the instance class. Everything else keeps today's leading swap.

Block selector Scoped result
.wp-block-list > li (List Item) .wp-block-list > .wp-states-xxxx
.wp-block-list:not(.wp-block-list .wp-block-list) > li (List Item border) .wp-block-list:not(.wp-block-list .wp-block-list) > .wp-states-xxxx
.wp-block-search .wp-block-search__input .wp-states-xxxx .wp-block-search__input, as today
.wp-block-button .wp-block-button__link .wp-states-xxxx .wp-block-button__link, as today

The ancestor is kept rather than dropped, so the state rule matches the same element as the non-state rule and keeps the same specificity.

The same logic lives on both sides: gutenberg_build_state_selector() in lib/block-supports/states.php, and buildScopedBlockSelector() in packages/block-editor/src/hooks/state-utils.js. Both now get the block name. No new API or block.json field.

Blocks covered: List Item, for its root selector, its border selector and responsive element styles. Button, Icon, Table, Paragraph, Search and every other library selector are unchanged, since their leading compound is the block's own class or not a wp-block-* class at all.

One edge: a third-party block that borrows another block's class as its root, with a bare element after it, would now be read as an ancestor.

Testing Instructions

Setup: a List with three items. On the middle one set a background colour, then the same with Mobile selected (View > Responsive styles > Mobile) in a different colour.

  1. List Item background: desktop value at desktop width, mobile value at mobile width.
    • Editor: switch the preview to Mobile.
    • Front end: resize below 480px.
  2. Repeat with text colour on a List Item. Same result.
  3. Paragraph with desktop and mobile background values. Unchanged, both apply at their own widths.
  4. Button with a hover background. Hovering the link shows it, in the editor and on the front end.
  5. Global Styles and Global Styles mobile values for List Item and Paragraph. Unchanged.

Unit tests: phpunit/block-supports/states-test.php and packages/block-editor/src/hooks/test/state-utils.js.

Screenshots or screencast

composite

Use of AI Tools

Implemented, built and screenshotted by a Claude Code agent from a predefined task.

@aaronrobertshaw aaronrobertshaw added [Feature] Design Tools Tools that impact the appearance of blocks both to expand the number of tools and improve the experi [Type] Enhancement A suggestion for improvement. [Package] Block library /packages/block-library labels Oct 1, 2026
@aaronrobertshaw aaronrobertshaw self-assigned this Oct 1, 2026
@aaronrobertshaw aaronrobertshaw added Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Package] Block editor /packages/block-editor labels Oct 1, 2026
@github-actions github-actions Bot removed the [Package] Block library /packages/block-library label Oct 1, 2026
@aaronrobertshaw
aaronrobertshaw marked this pull request as ready for review October 1, 2026 12:14
@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

🎉 Props

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: aaronrobertshaw <aaronrobertshaw@git.wordpress.org>
Co-authored-by: talldan <talldanwp@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

Updated as activity occurs, without notifying anyone named here. Add the props-bot label to refresh.

📦 Bundle size

Size Change: +225 B (0%)

Total Size: 8.29 MB

📦 View Changed
Filename Size Change
build/scripts/block-editor/index.min.js 517 kB +225 B (+0.04%)

44dde8e Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric 23d09fa trunk % Change
timeToFirstByte 57.9 ms +12.61% -4.92% 59.25 ms +3.38% -3.04% -2.28%
largestContentfulPaint 100 ms +6% -8% 98 ms +6.12% -4.08% 2.04%
lcpMinusTtfb 39.85 ms +12.42% -9.03% 36.9 ms +22.36% -5.15% 7.99%
wpBeforeTemplate 28 ms +17.46% -3.96% 28.49 ms +3.97% -1.54% -1.72%
wpTemplate 25.01 ms +6.12% -4.16% 26.2 ms +4.85% -3.78% -4.54%
wpTotal 53.78 ms +12.4% -4.76% 55.23 ms +3.53% -3.31% -2.63%
wpMemoryUsage 7.65 MB +0% -0% 7.61 MB +0% -0% 0.45%
wpDbQueries 17 +0% -0% 17 +0% -0% 0%

front-end-classic-theme

Metric 23d09fa trunk % Change
timeToFirstByte 43.45 ms +2.88% -5.06% 49.15 ms +5.39% -3.15% -11.6%
largestContentfulPaint 92 ms +4.35% -0% 108 ms +1.85% -3.7% -14.81%
lcpMinusTtfb 50.75 ms +1.28% -2.27% 57.4 ms +5.14% -3.4% -11.59%
wpBeforeTemplate 24.78 ms +3.75% -1.9% 25.06 ms +8.02% -3.27% -1.12%
wpTemplate 14.51 ms +5.1% -3.1% 20.01 ms +3.8% -2.55% -27.49%
wpTotal 40.36 ms +3.2% -5.15% 45.62 ms +5.7% -3.02% -11.53%
wpMemoryUsage 6.13 MB +0% -0% 6.23 MB +0% -0% -1.57%
wpDbQueries 10 +0% -0% 14 +0% -0% -28.57%

media-processing

Metric 23d09fa trunk % Change
mediaProcessingJpeg 417.38 ms +1.72% -0.63% 422.03 ms +1% -0.4% -1.1%
mediaProcessingAvif 6207.63 ms +0.21% -0.14% 6213.21 ms +0.29% -0.18% -0.09%
mediaProcessingJpegToAvif 4304.21 ms +0.2% -0.1% 4324.56 ms +0.17% -0.19% -0.47%

media-upload

Metric 23d09fa trunk % Change
jpegUploadProcessing 1454.16 ms +32.94% -2.35% 1442.72 ms +1.01% -0.91% 0.79%
pngUploadProcessing 202.75 ms +11.02% -10.77% 217.64 ms +6.86% -14% -6.84%
largeJpegUploadProcessing 1417.48 ms +1% -0.41% 1426.81 ms +0.78% -1.09% -0.65%
multipleImageUploadProcessing 1590.55 ms +9.82% -0.46% 1630.16 ms +9.89% -0.82% -2.43%

post-editor

Metric 23d09fa trunk % Change
serverResponse 413.43 ms +5.69% -3.75% 431.06 ms +4.95% -3.36% -4.09%
firstPaint 280.14 ms +18.81% -24.79% 192.9 ms +28.92% -8.54% 45.23%
domContentLoaded 1096.85 ms +4.24% -4.13% 1076.57 ms +0.63% -2.21% 1.88%
loaded 1098.3 ms +4.24% -4.14% 1077.95 ms +0.61% -2.21% 1.89%
firstContentfulPaint 465.03 ms +4.98% -9.71% 447.99 ms +3.14% -3.33% 3.8%
firstBlock 3205.45 ms +2.93% -2.93% 3139.81 ms +0.96% -0.23% 2.09%
type 19.85 ms +2.42% -4.48% 18.47 ms +4.66% -1.57% 7.47%
typeWithoutInspector 19.04 ms +13.34% -10.35% 18.61 ms +3.44% -5.64% 2.31%
typeWithTopToolbar 25.04 ms +4.11% -2.12% 24 ms +4.88% -3.17% 4.33%
typeContainer 8.27 ms +15.84% -2.18% 7.97 ms +4.39% -2.26% 3.76%
focus 73.69 ms +6.95% -5.4% 71.49 ms +10.09% -4.43% 3.08%
firstFocus 189.85 ms +0% -0% 201.08 ms +0% -0% -5.58%
selectAll 521.24 ms +4.8% -5.07% 518.5 ms +0.44% -3.19% 0.53%
listViewOpen 63.41 ms +6.07% -7.16% 58.73 ms +16.35% -11.46% 7.97%
inserterOpen 23.34 ms +9.47% -11.65% 23.12 ms +7.35% -9.43% 0.95%
inserterHover 2.26 ms +6.64% -6.64% 2.3 ms +18.7% -7.39% -1.74%
inserterSearch 8.48 ms +5.31% -4.6% 7.75 ms +5.81% -1.81% 9.42%
loadPatterns 658.5 ms +2.16% -1.6% 647.42 ms +1.93% -2.65% 1.71%
wpTotal 402 ms +5.94% -4.04% 419.57 ms +5.11% -3.29% -4.19%
wpMemoryUsage 13.20 MB +0% -0% 13.17 MB +0% -0% 0.25%
wpDbQueries 54 +0% -1.85% 54 +0% -1.85% 0%

site-editor

Metric 23d09fa trunk % Change
serverResponse 484.39 ms +5.12% -6.47% 506.33 ms +6.05% -8.34% -4.33%
firstPaint 224.6 ms +6.17% -4.51% 260.54 ms +10.28% -14.35% -13.79%
domContentLoaded 1143.68 ms +1.51% -0.72% 1146.4 ms +0.84% -1.04% -0.24%
loaded 1145.1 ms +1.5% -0.76% 1147.5 ms +0.85% -1.03% -0.21%
firstContentfulPaint 457.58 ms +1.02% -2.02% 460.9 ms +1.77% -1.26% -0.72%
firstBlock 4155.85 ms +1.81% -0.93% 4154.04 ms +0.42% -1.18% 0.04%
type 16.88 ms +5.04% -1.24% 17.17 ms +10.02% -0.87% -1.69%
navigate 109.63 ms +3.53% -2.69% 104.81 ms +8.45% -3.97% 4.6%
loadPatterns 1342.43 ms +2.44% -2.4% 1262.77 ms +6.59% -2.17% 6.31%
loadPages 1053.34 ms +1.04% -1.03% 1043.3 ms +2.07% -1.01% 0.96%
wpTotal 474.55 ms +5.21% -6.5% 496.49 ms +6.1% -8.49% -4.42%
wpMemoryUsage 12.25 MB +0% -0% 12.21 MB +0% -0% 0.3%
wpDbQueries 43 +2.33% -0% 43 +2.33% -0% 0%

44dde8e Run

Comment thread packages/block-editor/src/hooks/test/state-utils.js

@talldan talldan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it fixes it, and at least it's somewhat understandable why the list item selector is defined like it is given the block on the frontend has no classname of its own.

The selectors API is a bit worrying. It's simple on the surface which makes it seem like any selector whatsoever can be defined, but underneath there's increasing complexity (#78276, #78326 also) to support that simplicity.

Tough one to improve though, I don't really have any ideas. 🤷

The unit tests pass selectors as literals, so a change to a block's
`block.json` would not reach them. Two tests now exercise what the block
actually registers.

The PHP one reads `core/list-item`'s registered `root` and `border`
selectors and asserts the scoped result lands on the block's own element.
The end-to-end one gives a List Item a mobile background, narrows the
viewport and reads the computed colour off the `li`, with a paragraph
alongside it as a control. Both fail on trunk and pass here.
@aaronrobertshaw
aaronrobertshaw force-pushed the fix/states-selector-scoping branch from 459c870 to 44dde8e Compare October 6, 2026 08:44
@aaronrobertshaw

Copy link
Copy Markdown
Contributor Author

The selectors API is a bit worrying. It's simple on the surface which makes it seem like any selector whatsoever can be defined, but underneath there's increasing complexity

Yeah, this is an interesting case. As further features like block style variations, states, and responsive styles got added, what is being asked of that API is much more complex or demanding now.

I think a part of the problem is weird legacy choices for some blocks. Like styles landing on inner elements by default as if they were root selectors e.g Button block. Or, Paragraph and List Item blocks being elements and so on.

Things would be much simpler if every block had block class on its wrapper and a default or root selector that targets that. Then anything belonging on inner elements defined via the feature selectors in the Selectors API.

Like you said, I'm not seeing a huge number of alternatives or clean paths there.


For now, with the additional tests including an e2e added, I'll get this merged so the List Item block supports relying on it can work as intended. Happy to address anything further in a follow-up.

Thanks for the review and insight 👍

@aaronrobertshaw
aaronrobertshaw merged commit 928424a into trunk Oct 6, 2026
82 checks passed
@aaronrobertshaw
aaronrobertshaw deleted the fix/states-selector-scoping branch October 6, 2026 10:53
@github-actions github-actions Bot added this to the Gutenberg 24.2 milestone Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Feature] Design Tools Tools that impact the appearance of blocks both to expand the number of tools and improve the experi Global Styles Anything related to the broader Global Styles efforts, including Styles Engine and theme.json [Package] Block editor /packages/block-editor [Type] Enhancement A suggestion for improvement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants