Shield classic scrollbars from the fade, per axis - #10
Merged
Conversation
A mask on a scroll container also masks that container's scrollbar: masking applies after the element and its descendants draw to one temporary canvas, and a visible scrolling mechanism is part of that canvas. Overlay bars composite separately and escape, which is why this is invisible on a Mac and obvious on Windows. The strip's width cannot be derived. CSS Overflow 3 folds the reserved gutter into the padding area for background positioning, and CSS Masking defines mask boxes as the background boxes, so no <coord-box> isolates it; container query units measure it but resolve against an ancestor, so only descendants can read them. So fade-scrollbar-safe-* does not measure the bar, it declares it: pins via ::-webkit-scrollbar and sizes the strip from the same custom property, which makes them equal by construction and keeps them aligned under page zoom. Added: - fade-scrollbar-safe-y / -x / -xy. The suffix names the SCROLL axis, following overflow-y/overflow-x -- deliberately the opposite convention from this plugin's own fade-y, which names faded edges. Only -y and -xy stabilize a gutter; -x must not, or it would reserve an inline gutter for a vertical bar the author just said does not exist and put the strip on content. - fade-scrollbar-width-sm|md|lg (11/15/17px) over a new --fade-scrollbar-width-* namespace. Absolute px, not the spacing ramp: a scrollbar is device chrome, not typographic rhythm. A pure setter, and inheriting, so a house style can live on an ancestor. Fixed: - The strips no longer make @Property load-bearing for the whole plugin. mask-size referenced --tw-fade-scrollbar, which is only declared inside @supports selector(::-webkit-scrollbar); elsewhere its value came solely from the @Property initial value. Without @Property that reference is guaranteed-invalid, which invalidated the entire mask-size declaration at computed-value time -- all six layers fell to auto, the two 0px strips became full-size opaque layers, and the fade vanished. Every reference now carries an explicit fallback, verified by re-rendering with @Property stripped out. - The inline-end ramp now reaches transparent on the content edge, not inside the gutter (measured 40/255 -> 0/255 at a 60px band with a 15px bar). - The strip is anchored to the padding box, so it tracks the gutter through a border instead of straddling it. Docs corrected against the code while documenting the feature: the Exports support contract excluded the feature's own two knobs, the Safari no-gutter limit understated when it fires (the static fallback pins fades on regardless of overflow on Safari 17.x/18.x), the collision warning named a Tailwind utility that does not exist while omitting scrollbar-thumb-* / -track-*, "How It Works" still described a four-layer mask, and two stale measured figures predated the 0.7.0 onset/alpha decouple. Verification lives in build/verify-scrollbar.mjs: headed Chromium, real pixels, nine areas. Headed on purpose -- headless reserves a gutter without painting a bar, and reserves nothing at all for a horizontal one, so every assertion would pass vacuously.
Without animation-timeline: scroll() the plugin takes its static fallback, which pins every selected fade ON regardless of whether the container overflows. A faded container with no overflow has no reserved gutter, so the strip lands on content. Measured in WebKit: a plain `overflow-auto fade-y fade-scrollbar-safe-y` whose content does not overflow fades its inline end to fully transparent and then paints a hard 15px opaque band of content beyond it -- the exact inversion of what the utility is for, on markup that never mentions fade-always. Gate the whole opt-in on @supports (animation-timeline: scroll()), pin included. Dropping only the strip is worse than useless: the pin is what makes the bar classic, so pinned-but-unshielded manufactures a space-consuming bar on macOS -- a platform with no bug -- and then lets the fade dim it. Safari 17.x/18.x and release Firefox now keep their native scrollbars instead. This costs nothing where the feature works, since every engine that can shield a bar (Blink, WebKit 26+) has scroll-driven animations. Rejected on measurement: multiplying the strip extents by max(--tw-fade-t, -b, -l, -r) so they collapse when no edge is faded. The slab cases all have amounts pinned to 1 -- fade-always, the static fallback, or a live cross-axis fade -- so the factor is 1 exactly when the bug fires. The only case it changes is an opted-in container with no active fade, which is already correct because the inline-end inset and the strip cancel each other there. The residual on WebKit 26+ is not fixable in CSS: the gutter's existence is not queryable, which is the same wall the strip's width ran into. An author can close it with overflow-y: scroll, which forces the gutter in WebKit too -- measured, 15px reserved with the strip landing exactly on the track. Documented in Limits alongside the stand-down. Verified two ways. Section 10 rewrites the feature query in the shipped CSS so a modern engine takes the old engine's branch, then asserts no gutter, no pin and no strip -- and asserts the fallback still pins fades on, so the section fails loudly rather than vacuously if that premise ever changes. Section 11 drives Playwright WebKit and pins both halves of the documented residual: overflow-auto still reserves nothing, overflow-y: scroll still reserves 15px. Both new sections fail against the pre-fix CSS (verified by running the harness against the previous dist) and pass against this one. A CSS-text test locks the nesting so no rule can escape the gate, since no engine the harness can run reaches that path.
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.
A mask on a scroll container also masks that container's scrollbar: masking applies after the element and its descendants draw to one temporary canvas, and a visible scrolling mechanism is part of that canvas. Overlay bars composite separately and escape, which is why this is invisible on a Mac and obvious on Windows.
The strip's width cannot be derived. CSS Overflow 3 folds the reserved gutter into the padding area for background positioning, and CSS Masking defines mask boxes as the background boxes, so no isolates it; container query units measure it but resolve against an ancestor, so only descendants can read them. So fade-scrollbar-safe-* does not measure the bar, it declares it: pins via ::-webkit-scrollbar and sizes the strip from the same custom property, which makes them equal by construction and keeps them aligned under page zoom.
Added:
Fixed:
Docs corrected against the code while documenting the feature: the Exports support contract excluded the feature's own two knobs, the Safari no-gutter limit understated when it fires (the static fallback pins fades on regardless of overflow on Safari 17.x/18.x), the collision warning named a Tailwind utility that does not exist while omitting scrollbar-thumb-* / -track-*, "How It Works" still described a four-layer mask, and two stale measured figures predated the 0.7.0 onset/alpha decouple.
Verification lives in build/verify-scrollbar.mjs: headed Chromium, real pixels, nine areas. Headed on purpose -- headless reserves a gutter without painting a bar, and reserves nothing at all for a horizontal one, so every assertion would pass vacuously.