Skip to content

fix(fabric): force overlay scrollbar style in ScrollView#2907

Merged
Saadnajmi merged 2 commits intomainfrom
scrollbar-overlay-only
Apr 11, 2026
Merged

fix(fabric): force overlay scrollbar style in ScrollView#2907
Saadnajmi merged 2 commits intomainfrom
scrollbar-overlay-only

Conversation

@Saadnajmi
Copy link
Copy Markdown
Collaborator

@Saadnajmi Saadnajmi commented Apr 9, 2026

Summary

  • Force NSScrollerStyleOverlay on Fabric ScrollViews to fix layout overflow on first render
  • Remove autoresizingMask from documentView to prevent AppKit frame corruption
  • Fix scrollbar click hit testing (check NSScroller before content subviews)
  • Re-force overlay style when the system "Show scroll bars" preference changes at runtime

Why force overlay instead of supporting legacy scrollbars?

  1. Every other platform uses overlay scrollbars. iOS, Android, and web all render scrollbar indicators that float above content. macOS is the only platform where scrollbars can sit inside the frame and reduce the visible content area. Forcing overlay aligns macOS behavior with every other React Native platform.

  2. Supporting legacy scrollbars required invasive changes to ReactCommon. The alternative approach required adding cached atomic values, custom shadow node constructors, and padding adjustments in the Yoga layout pass at the ScrollViewShadowNode C++ layer — a significant cross-platform change to support a single-platform edge case.

  3. Apple themselves call non-overlay scrollbars "legacy." The API is literally NSScrollerStyleLegacy. Mac Catalyst doesn't even respect this system preference (it always uses overlay). SwiftUI does respect it, but SwiftUI also has the advantage of proposing clip view size to children — something React Native's layout system doesn't do.

Test plan

  • Open RNTester on macOS with "Show scroll bars: Always" in System Settings
  • Verify ScrollView content does not overflow on first render
  • Verify scrollbars appear as overlay style (thin, semi-transparent)
  • Verify switching system preference at runtime doesn't bring back legacy scrollbars
  • Verify scrollbar clicks work (drag the scrollbar thumb)
  • Verify window resize doesn't cause content overflow
  • Test with "Show scroll bars: When scrolling" (default) — unchanged behavior

Fixes #2857

🤖 Generated with Claude Code

@Saadnajmi Saadnajmi requested a review from a team as a code owner April 9, 2026 21:05
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 9, 2026

⚠️ No Changeset found

Latest commit: ef37f10

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@Saadnajmi Saadnajmi changed the title fix(fabric,macos): force overlay scrollbar style in ScrollView fix(fabric): force overlay scrollbar style in ScrollView Apr 9, 2026
@Saadnajmi
Copy link
Copy Markdown
Collaborator Author

/backport 0.81-stable

@microsoft-react-native-sdk
Copy link
Copy Markdown

Backport results

  • ⚠️ 0.81-stable : branch does not exist

Force NSScrollerStyleOverlay on Fabric ScrollViews to avoid layout
issues with legacy (always-visible) scrollbars. Legacy scrollbars sit
inside the NSScrollView frame and reduce the clip view's visible area,
which would require compensating padding in the Yoga shadow tree.
Overlay scrollers float above content, so no layout compensation is
needed.

Additional fixes:
- Remove autoresizingMask from documentView to prevent AppKit frame
  corruption during tile/resize
- Remove the layoutSubviews workaround (no longer needed without
  autoresizingMask)
- Add [_scrollView tile] after content size updates to re-evaluate
  scroller visibility
- Fix hit testing: check NSScroller before content subviews so
  scrollbar clicks aren't swallowed by full-width content views

Fixes #2857

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Minimize verbose comments throughout
- Remove explicit [_scrollView tile] call in updateState: (unnecessary
  with overlay scrollbar style)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Saadnajmi Saadnajmi merged commit a054786 into main Apr 11, 2026
17 checks passed
@Saadnajmi Saadnajmi deleted the scrollbar-overlay-only branch April 11, 2026 04:07
Saadnajmi added a commit that referenced this pull request Apr 11, 2026
## Summary
Backport of #2907 to `0.81-stable`.

- Force `NSScrollerStyleOverlay` on Fabric ScrollViews to fix layout
overflow on first render
- Remove `autoresizingMask` from documentView to prevent AppKit frame
corruption
- Fix scrollbar click hit testing (check `NSScroller` before content
subviews)
- Re-force overlay style when the system "Show scroll bars" preference
changes at runtime

## Why force overlay instead of supporting legacy scrollbars?

1. **Every other platform uses overlay scrollbars.** iOS, Android, and
web all render scrollbar indicators that float above content. Forcing
overlay aligns macOS with every other React Native platform.

2. **Supporting legacy scrollbars required invasive changes to
ReactCommon.** The alternative required cached atomic values, custom
shadow node constructors, and padding adjustments in the Yoga layout
pass — a significant cross-platform change for a single-platform edge
case.

3. **Apple themselves call non-overlay scrollbars "legacy."** The API is
literally `NSScrollerStyleLegacy`. Mac Catalyst doesn't even respect
this preference (always uses overlay).

## Test plan
- [x] Verified on macOS with "Show scroll bars: Always" — no overflow on
first render
- [x] Verified switching system preference at runtime doesn't bring back
legacy scrollbars
- [x] Verified scrollbar clicks work
- [x] Verified window resize doesn't cause overflow

Fixes #2857

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <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.

Text inside the ScrollView has massive vertical and horizontal overflow on the first render

2 participants