Skip to content

Add ::scroll-marker, ::scroll-marker-group and ::scroll-button() - #1321

Open
oddharsh wants to merge 1 commit into
parcel-bundler:masterfrom
oddharsh:feat/scroll-navigation-control-pseudo-elements
Open

oddharsh wants to merge 1 commit into
parcel-bundler:masterfrom
oddharsh:feat/scroll-navigation-control-pseudo-elements

Conversation

@oddharsh

@oddharsh oddharsh commented Sep 2, 2026

Copy link
Copy Markdown

Closes #1177. Part of #1184.

Problem

::scroll-marker, ::scroll-marker-group and ::scroll-button() are not recognized, so a stylesheet using the CSS Overflow 5 carousel pattern gets three is not recognized as a valid pseudo-element warnings on correct CSS. They parse as Custom and pass through, so only the diagnostic is wrong.

The three :target-* pseudo-classes from the same spec section landed in #1185, and the SCROLL_NAVIGATION_CONTROLS flag added there already points at the section that defines these. This fills in the rest of it.

Changes

The three pseudo-elements, behind the existing flag. ScrollMarker, ScrollMarkerGroup and ScrollButton { direction } are real variants with their own serialization, gated on SCROLL_NAVIGATION_CONTROLS exactly as :target-current is. Nothing changes with the flag off.

::scroll-button() takes '*' | <scroll-button-direction>. ScrollButtonDirection carries the ten keywords the spec defines (up, down, left, right, block-start, block-end, inline-start, inline-end, prev, next) plus *, matched case-insensitively and serialized canonically, so ::SCROLL-BUTTON(BLOCK-START) prints as ::scroll-button(block-start). An unknown keyword is a SelectorError::UnexpectedIdent rather than a silent Custom.

A second fix that the first one needs: ::scroll-marker:target-current was rejected. Selectors 4 allows only the user action pseudo-classes after a pseudo-element, and css-overflow-5 defines the three :target-* pseudo-classes specifically to match scroll markers, so this is the shape the feature is actually written in. It went through a new AFTER_SCROLL_MARKER parsing state and a NonTSPseudoClass::is_valid_after_scroll_marker with a default of is_user_action_state(), which mirrors how AFTER_WEBKIT_SCROLLBAR and is_valid_after_webkit_scrollbar already handle the same kind of spec-defined exception. Both trait methods are additive with defaults, so no other implementor changes.

The narrow shape is deliberate. A blanket widening was the first thing I tried and it turned #1185's a::before:target-current error test green, which is the wrong answer; keying on the preceding pseudo-element keeps that an error, and its test is unchanged and still passing.

compat.rs is generated, not hand-edited. Three mdnFeatures entries in scripts/build-prefixes.js, then node scripts/build-prefixes.js. All three are Chrome 135, no Firefox or Safari. The only change the run produced was those features, so the diff carries no unrelated churn.

Testing

cargo test -p lightningcss passes, 120 of 120, with the new cases in test_selectors: each pseudo-element, * and a flow-relative keyword, the case-insensitivity round trip, ::scroll-marker:target-current, a::before:target-current still erroring, an unknown direction erroring, and both pass-through cases with the flag off so the no-flag behaviour is pinned.

Two things worth flagging rather than hiding. cargo test -p parcel_selectors has one failure, parser::tests::test_parsing asserting parse("foo::details-content").is_ok(); it fails identically on an unpatched checkout of this branch's base, so it is pre-existing. And cargo test --workspace cannot link lightningcss_node outside a Node build in my environment (undefined napi symbols), which is unrelated to this diff.

Downstream

Bun ports this parser, and its table trails yours: oven-sh/bun#41120, where oven-sh/bun#41122 is adding the four entries you already have. They are holding the scroll-marker family at parity with lightningcss deliberately, so this reaching upstream is what carries it to them.

These are the pseudo-elements of css-overflow-5's scroll navigation
controls, the same spec section the existing SCROLL_NAVIGATION_CONTROLS
flag already gates :target-current, :target-before and :target-after
behind, so they are gated the same way.

Also allows the three :target-* pseudo-classes after ::scroll-marker.
Selectors 4 permits only the user action pseudo-classes after a
pseudo-element, and css-overflow-5 defines these three specifically to
match scroll markers, so ::scroll-marker:target-current has to parse.
That goes through a new AFTER_SCROLL_MARKER parsing state, mirroring how
AFTER_WEBKIT_SCROLLBAR already handles the same kind of exception.
a::before:target-current stays an error.
@oddharsh

Copy link
Copy Markdown
Author

Re-verified on master (c6a0c3c) today, through a node binding built from it. All three still warn while being emitted verbatim:

'scroll-marker' is not recognized as a valid pseudo-element. Did you mean ':scroll-marker' (pseudo-class) or is this a typo?
'scroll-button' is not recognized as a valid pseudo-element. Did you mean ':scroll-button' (pseudo-class) or is this a typo?
'target-current' is not recognized as a valid pseudo-class. Did you mean '::target-current' (pseudo-element) or is this a typo?

The pass-through is why this is advisory rather than breaking, and it is also why it is easy to leave: a build that treats warnings as errors fails on valid CSS Overflow 5, and one that does not carries a warning per carousel selector per build forever.

Still applies cleanly and the suite is green on it. Happy to rebase whenever it is useful.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CSS Parsing error for ::scroll-marker

1 participant