Skip to content

feat(tabs): scroll fade for overflowing segmented tabs#504

Open
mattrothenberg wants to merge 3 commits intomainfrom
feat/tabs-scroll-fade
Open

feat(tabs): scroll fade for overflowing segmented tabs#504
mattrothenberg wants to merge 3 commits intomainfrom
feat/tabs-scroll-fade

Conversation

@mattrothenberg
Copy link
Copy Markdown
Collaborator

@mattrothenberg mattrothenberg commented May 8, 2026

Summary

  • Segmented tabs now scroll horizontally when overflowing, with gradient fades on the edges that respond to scroll position

  • Uses scroll-driven animations (animation-timeline: scroll()) — Chrome 115+, degrades gracefully to no fade in unsupported browsers

  • ResizeObserver detects overflow and sets data-overflowing via React state to activate the fade

  • Scrollbar hidden — the fade is the scroll affordance

  • overscroll-behavior-x: contain prevents page scroll when hitting tab edges

  • Baked into all segmented tabs automatically — no prop needed

  • Reviews

  • bonk has reviewed the change

  • automated review not possible because: visual/interaction change needs manual review

  • Tests

  • Additional testing not necessary because: CSS-only fade with JS overflow detection, no logic changes to tab behavior

Segmented tabs now scroll horizontally when overflowing, with gradient
fades on the edges driven by scroll-driven animations (Chrome 115+,
degrades gracefully). Scrollbar is hidden — the fade is the affordance.

- scroll-fade-x CSS in kumo-binding.css using mask-image + animation-timeline
- ResizeObserver sets data-overflowing attribute to activate the fade
- overscroll-behavior-x: contain prevents page scroll at edges
- Ported from stratus RouteTabs implementation by Balint
@mattrothenberg
Copy link
Copy Markdown
Collaborator Author

/bonk please review

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 8, 2026

I've reviewed PR #504. Here's my analysis:

Summary

This PR adds horizontal scroll fade functionality to segmented tabs, ported from the RouteTabs implementation in stratus. The implementation uses scroll-driven animations with graceful degradation for unsupported browsers.

Code Review

.changeset/tabs-scroll-fade.md

  • Correct minor bump for @cloudflare/kumo
  • Clear description of the feature

packages/kumo/src/components/tabs/tabs.tsx

useOverflowDetect hook (lines 227-253):

  • Clean, focused custom hook using ResizeObserver
  • Properly cleans up observer on unmount
  • Only runs when enabled is true (segmented variant)
  • Sets data-overflowing attribute to drive CSS animations

Integration (lines 153, 169, 173):

  • useRef properly typed as HTMLDivElement
  • CSS class kumo-tabs-list added to the list element
  • CSS custom property --scroll-fade-width: 3rem sets fade width

packages/kumo/src/styles/kumo-binding.css

Scroll-driven animations (lines 217-273):

  • Uses @supports (animation-timeline: scroll()) for feature detection
  • Smart use of mask-image with 3-layer gradient technique:
    • Left fade (white → transparent)
    • Center content (solid white)
    • Right fade (transparent → white)
  • mask-composite: exclude properly combines the layers
  • animation-fill-mode: both ensures proper state at scroll boundaries
  • animation-composition: replace handles overlapping animations correctly
  • overscroll-behavior-x: contain prevents page scroll when hitting tab edges
  • Hidden scrollbar via standard CSS properties

Observations

  1. Progressive enhancement: The fade only appears in Chrome 115+/Edge 115+ with scroll-driven animation support. Other browsers show standard scrollbar-hidden scrolling — acceptable UX degradation.

  2. Performance: ResizeObserver is efficient for detecting overflow changes without polling.

  3. CSS technique: The mask-based approach is clever — no extra DOM elements needed for the fade effect, and it overlays the tab content naturally.

  4. Nit: The check callback in useOverflowDetect could use el.scrollWidth > el.clientWidth + 1 to account for subpixel rounding, but this is minor.

Approval

This is a well-implemented feature that:

  • Follows Kumo conventions (semantic tokens, cn() utility)
  • Uses modern CSS with proper feature detection
  • Maintains accessibility (tabs are still keyboard navigable)
  • Has minimal bundle impact

bonk approves this PR

The implementation is solid and ready to merge.

github run

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 8, 2026

npm i https://pkg.pr.new/@cloudflare/kumo@504

commit: 7e35550

@mattrothenberg
Copy link
Copy Markdown
Collaborator Author

/bonk can you approve

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Docs Preview

View docs preview

Commit: a43887a

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

bonk approves ✅

The scroll fade implementation for segmented tabs is well-executed:

  • Clean hook with proper ResizeObserver cleanup
  • Progressive enhancement via
  • Clever mask-image technique with no extra DOM elements
  • Proper for UX
  • Follows Kumo conventions throughout

Ready to merge.

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

bonk approves ✅

The scroll fade implementation for segmented tabs is well-executed:

  • Clean useOverflowDetect hook with proper ResizeObserver cleanup
  • Progressive enhancement via @supports animation-timeline
  • Clever mask-image technique with no extra DOM elements
  • Proper overscroll-behavior-x for UX
  • Follows Kumo conventions throughout

Ready to merge.

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented May 8, 2026

bonk has approved PR #504

The scroll fade feature for segmented tabs is approved and ready to merge.

github run

Unsupported browsers (Firefox, older Safari) keep the native scrollbar
as a fallback affordance since they do not get the fade.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Visual Regression Report — 10 changed, 28 unchanged

10 screenshot(s) with visual changes:

Button / Icon Only

32 px (0.03%) changed

Before After Diff
Before After Diff

Button / Loading State

8 px (0.01%) changed

Before After Diff
Before After Diff

Dialog / Dialog Alert

354 px (0.35%) changed

Before After Diff
Before After Diff

Dialog / Dialog Confirmation

2,102 px (2.07%) changed

Before After Diff
Before After Diff

Select / Select Basic

299 px (0.29%) changed

Before After Diff
Before After Diff

Select / Select Sizes

170 px (0.09%) changed

Before After Diff
Before After Diff

Select / Select With Field

795 px (0.67%) changed

Before After Diff
Before After Diff

Select / Select Loading

0 px (0%) changed

Before After Diff
Before After Diff

Select / Select Grouped With Disabled

673 px (0.66%) changed

Before After Diff
Before After Diff

Select (Open)

0 px (0%) changed

Before After Diff
Before After Diff
28 screenshot(s) unchanged
  • Button / Basic
  • Button / Variant: Primary
  • Button / Variant: Secondary
  • Button / Variant: Ghost
  • Button / Variant: Destructive
  • Button / Variant: Outline
  • Button / Variant: Secondary Destructive
  • Button / Sizes
  • Button / With Icon
  • Button / Disabled State
  • Button / Title
  • Button / Link as Button
  • Dialog / Dialog With Actions
  • Dialog / Dialog Basic
  • Dialog / Dialog With Select
  • Dialog / Dialog With Combobox
  • Dialog / Dialog With Dropdown
  • Dialog (Open)
  • Select / Select Without Label
  • Select / Select Placeholder
  • Select / Select With Tooltip
  • Select / Select Custom Rendering
  • Select / Select Multiple
  • Select / Select Complex
  • Select / Select Disabled Options
  • Select / Select Disabled Items
  • Select / Select Grouped
  • Select / Select Long List

Generated by Kumo Visual Regression

mattrothenberg added a commit that referenced this pull request May 9, 2026
- Use full-size segmented Tabs in PageTabs (drop size="sm")
- Tabs auto-overflow horizontally via PR #504 scroll-fade
- Tab CTAs collapse into a DropdownMenu ellipsis on small screens
- Move data-mode="dark" from wrapper div to <html> so portaled
  overlays inherit dark theme

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

1 participant