Skip to content

[TimelineItem] Keep the spacer selector at one class of specificity - #49028

Open
Janpot wants to merge 2 commits into
mui:masterfrom
Janpot:code-infra/timeline-before-specificity
Open

[TimelineItem] Keep the spacer selector at one class of specificity#49028
Janpot wants to merge 2 commits into
mui:masterfrom
Janpot:code-infra/timeline-before-specificity

Conversation

@Janpot

@Janpot Janpot commented Aug 24, 2026

Copy link
Copy Markdown
Member

:has() and :not() take the specificity of their argument, so the ::before spacer rule added in #46663 sits at (0,2,1) — exactly level with the Timeline override 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: ::before flex-grow is 1 rendered alone, 0 after 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 the globalSelector stylis 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(…)::before matching 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.hasOppositeContent at 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-grow is 0 in 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.

@code-infra-dashboard

code-infra-dashboard Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deploy preview

https://deploy-preview-49028--material-ui.netlify.app/
QR code for https://deploy-preview-49028--material-ui.netlify.app/

Bundle size

Bundle Parsed size Gzip size
@mui/material 0B(0.00%) 0B(0.00%)
@mui/lab 🔺+51B(+0.15%) 🔺+17B(+0.20%)
@mui/private-theming 0B(0.00%) 0B(0.00%)
@mui/system 0B(0.00%) 0B(0.00%)
@mui/utils 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@Janpot Janpot added the package: lab Specific to the lab. label Aug 24, 2026
@Janpot
Janpot marked this pull request as ready for review August 24, 2026 17:35

@LukasTy LukasTy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It completely breaks the docs layout. 🙈

Image

@silviuaavram

silviuaavram commented Aug 25, 2026

Copy link
Copy Markdown
Member

Confirmed as well. Tried removing the where() and it got back to normal.

Shouldn't this be caught by automation? We definitely have a gap.

@LukasTy

LukasTy commented Aug 25, 2026

Copy link
Copy Markdown
Member

Shouldn't this be caught by automation? We definitely have a gap.

Automation runs each demo and some explicit docs components individually in isolation.
In this case a docs demo code leaked it's styles to whole docs, but our regressions don't notice this since they render only individual demos.

@Janpot

Janpot commented Aug 25, 2026

Copy link
Copy Markdown
Member Author

Opening #49029 as a potential fix for the docs
Opening mui/mui-public#1811 to track filling the regression testing gap

Janpot added 2 commits August 25, 2026 12:06
`: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
Janpot force-pushed the code-infra/timeline-before-specificity branch from 7cbc769 to 857c417 Compare August 25, 2026 10:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: lab Specific to the lab.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants