[TimelineItem] Keep the spacer selector at one class of specificity - #49028
Open
Janpot wants to merge 2 commits into
Open
[TimelineItem] Keep the spacer selector at one class of specificity#49028Janpot wants to merge 2 commits into
Janpot wants to merge 2 commits into
Conversation
Deploy previewBundle size
Check out the code infra dashboard for more information about this PR. |
Member
|
Confirmed as well. Tried removing the Shouldn't this be caught by automation? We definitely have a gap. |
Member
Automation runs each demo and some explicit docs components individually in isolation. |
This was referenced Aug 25, 2026
Member
Author
|
Opening #49029 as a potential fix for the docs |
`:has()` and `:not()` take the specificity of their argument, so the `::before` spacer rule added in mui#46663 sits at (0,2,1) -- level with the `Timeline` override the docs recommend for removing it: sx={{ [`& .${timelineItemClasses.root}:before`]: { flex: 0, padding: 0 } }} A tie is settled by stylesheet order, and emotion inserts a class the first time it renders. So the override wins only when `TimelineItem`'s own class was already inserted by an earlier Timeline on the page, and loses when the demo is the first one rendered. In the docs that reads as the demo silently losing its override; in the visual regression suite, where pages are pooled across concurrent tests, the NoOppositeContent screenshot flips between runs. Wrapping the match in `:where()` keeps the `:has()` behaviour -- opposite content still counts when it is not a direct child, which is why mui#46663 moved off the children walk -- while contributing no specificity, putting the rule back at (0,1,1) so the documented override always wins. Both properties are now covered: one test asserts the override applies, the other that nested opposite content still suppresses the spacer. mui#46663 shipped without a test, and reverting it fails the second.
The `:where()` form broke every docs page: the docs' emotion cache runs the `globalSelector` stylis middleware (core-docs, a workaround for emotion-js/emotion#2836) which strips the class in front of any `:where(`/`:is(` occurrence. That turned the scoped spacer rule into a bare `:where(:not(:has(...)))::before` matching nearly every element on mui.com. `:where()` cannot be used in component styles while that middleware exists. Same outcome by other means: the spacer is emitted from `ownerState.hasOppositeContent` at plain `&::before` -- (0,1,1), so the documented `Timeline` override always wins -- and a `&:has(...)::before { content: none }` rule at (0,2,1) keeps covering opposite content that is not a direct child, which the children walk cannot see (mui#46663).
Janpot
force-pushed
the
code-infra/timeline-before-specificity
branch
from
August 25, 2026 10:07
7cbc769 to
857c417
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

:has()and:not()take the specificity of their argument, so the::beforespacer rule added in #46663 sits at (0,2,1) — exactly level with theTimelineoverride the docs recommend for removing it. A tie is settled by emotion's insertion order, so the override wins only when another Timeline demo already rendered on the page. In the docs that reads as demos silently losing their override; in the VRT suite, where pages are pooled across concurrent tests, the NoOppositeContent screenshot flips between runs (measured:::beforeflex-growis1rendered alone,0after any other Timeline demo).First attempt used
&:where(:not(:has(…)))to zero out the specificity, which broke every docs page: the docs' emotion cache runs theglobalSelectorstylis middleware (core-docs, a workaround for emotion-js/emotion#2836) that strips the class in front of any:where(/:is(, turning the rule into an unscoped:where(…)::beforematching nearly every element. So:where()/:is()can't currently appear in component styles at all — worth hardening that middleware separately.Final shape: the spacer is emitted from
ownerState.hasOppositeContentat plain&::before— (0,1,1), so the documented override always wins deterministically — plus&:has(…)::before { content: none }at (0,2,1) to keep covering opposite content that isn't a direct child, which the children walk can't see (that's what #46663 fixed). Verified:flex-growis0in every render order, docs pages carry zero stray::befores (checked against the local docs server, which runs the middleware), and the homepage hero card measures identical to master.This changes the NoOppositeContent screenshot: it now renders left-flush, which is what the demo is meant to show and what mui.com renders — the current Argos baseline is the broken render. #46663 shipped without a test; both properties are now covered, and each test fails without its half of the fix.