Skip to content

feat(ScrollArea): add fade prop for fading scrollable sides, migrate Table fade to scroll-driven animations#648

Merged
blvdmitry merged 6 commits into
canaryfrom
claude/scrollarea-fade-mask-prop-kbq5f9
Jul 2, 2026
Merged

feat(ScrollArea): add fade prop for fading scrollable sides, migrate Table fade to scroll-driven animations#648
blvdmitry merged 6 commits into
canaryfrom
claude/scrollarea-fade-mask-prop-kbq5f9

Conversation

@blvdmitry

@blvdmitry blvdmitry commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a new fade boolean prop to ScrollArea that displays a fade mask on the sides of the area that can be scrolled towards, and migrates the existing Table scroll fade to the same technique.

The implementation is CSS-only, built on scroll-driven animations instead of the JS-toggled class approach used by the older fade implementations:

  • Registered @property custom properties (--rs-scroll-area-fade-{block,inline}-{start,end}, --rs-table-fade-{start,end}) drive the stop positions of mask-image linear gradients (ScrollArea combines the two axes with mask-composite: intersect). The alpha falloff is eased (smoothstep approximated with intermediate stops at fractions of the fade size) rather than a linear ramp.
  • Each fade size is animated with animation-timeline: scroll(self block/inline) and an animation-range covering the first/last fade-size distance of the scroll range, so a fade appears only on the sides with remaining scrollable content and reacts to scrolling without any JS.
  • Scroll timelines stay inactive for axes without overflow, so non-scrollable sides render no fade at all.
  • In ScrollArea, the mask is applied to the internal scrollable element, which the custom scrollbars are siblings of — the scrollbars stay unmasked.
  • RTL is handled by flipping the inline gradient direction under [dir="rtl"].
  • The fade blocks are wrapped in @supports (animation-timeline: ...): browsers without scroll-driven animations support (e.g. current stable Firefox) render without the fade.

Table migration: Table no longer uses the useFadeSide hook — its scroll/resize listeners, React state updates and JS RTL scrollLeft handling are removed, along with the --fade-start/--fade-end classes and the @property transition. The fade now also grows proportionally with the scroll position and uses the same eased gradient as ScrollArea. useFadeSide itself is kept since Tabs still uses it for its scroll control buttons. Note this changes Table's fade behavior in stable Firefox (no fade until it ships scroll-driven animations), where the previous JS implementation rendered one.

Also adds a fade Storybook story and changesets (minor for the ScrollArea prop, patch for the Table refactor).

Related Issue

N/A

Screenshots / Recordings

Verified in Chromium via Storybook, for both ScrollArea and Table:

  • At scroll start: fade only on the end sides; both fades while mid-scroll; fade disappears on a side once fully scrolled towards it.
  • No fade at all when the content doesn't overflow.
  • RTL: horizontal fade renders on the correct logical side.

Notes for Reviewers

  • The fade sizes can be themed by overriding --rs-scroll-area-fade-size / --rs-table-fade-size (set on the root elements; Table keeps its previous --rs-unit-x4, ScrollArea uses --rs-unit-x6).
  • @property initial values keep the fades at 0, so even where the mask applies but animations somehow don't run, the mask resolves to fully opaque (no visual change).
  • ScrollArea (8/8) and Table (9/9) storybook tests pass, plus stylelint/oxlint/tsc.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VTo6CXkUBtLeftQaZ9RU4X

claude added 2 commits July 2, 2026 20:40
Uses CSS scroll-driven animations to display the fade mask without any
JS: scroll timelines animate registered custom properties used as mask
gradient stops, so the fade only appears on the sides that can be
scrolled towards. The mask is applied to the scrollable element only,
keeping the custom scrollbars unmasked. Browsers without support render
the scroll area without the mask.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTo6CXkUBtLeftQaZ9RU4X
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTo6CXkUBtLeftQaZ9RU4X
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size Loading time (3g) Running time (snapdragon) Total time
Library / JS 48.96 KB (-0.05% 🔽) 980 ms (-0.05% 🔽) 342 ms (+3.88% 🔺) 1.4 s
Library / CSS 22.98 KB (+2.51% 🔺) 460 ms (+2.51% 🔺) 0 ms (+100% 🔺) 460 ms
Theming / JS 7.81 KB (0%) 157 ms (0%) 461 ms (+1811.48% 🔺) 617 ms
Theming with a default theme definition / JS 8.65 KB (0%) 174 ms (0%) 288 ms (+384.56% 🔺) 461 ms

@blvdmitry blvdmitry changed the title feat(ScrollArea): add fadeMask prop for fading scrollable sides feat(ScrollArea): add fade prop for fading scrollable sides Jul 2, 2026
@blvdmitry blvdmitry changed the base branch from main to canary July 2, 2026 20:58
@blvdmitry blvdmitry marked this pull request as ready for review July 2, 2026 21:01
claude added 2 commits July 2, 2026 21:02
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTo6CXkUBtLeftQaZ9RU4X
Replaces the useFadeSide scroll tracking with the same CSS-only
approach used by the ScrollArea fade prop, removing the scroll and
resize listeners along with the JS RTL handling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VTo6CXkUBtLeftQaZ9RU4X
@blvdmitry blvdmitry changed the title feat(ScrollArea): add fade prop for fading scrollable sides feat(ScrollArea): add fade prop for fading scrollable sides, migrate Table fade to scroll-driven animations Jul 2, 2026
@blvdmitry blvdmitry merged commit 144bbda into canary Jul 2, 2026
12 checks passed
@blvdmitry blvdmitry deleted the claude/scrollarea-fade-mask-prop-kbq5f9 branch July 2, 2026 21:37
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.

2 participants