Skip to main content

Scroll coordination

scrollCoordination controls how vertical gestures that start inside a nested scrollable, such as a ScrollView or FlatList, interact with the sheet. Expansion and collapse can be configured independently:

<BottomSheet
scrollCoordination={{
expand: 'handoff',
collapse: 'initial',
}}
>
{/* ... */}
</BottomSheet>

The default is { expand: 'handoff', collapse: 'initial' }. This lets an upward drag expand the sheet and continue into the list without lifting, while a downward drag chooses either the list or the sheet when the gesture begins. It avoids handing a downward list gesture to the sheet midway through the drag, which can otherwise cause an accidental collapse.

Modes​

Each direction accepts one of three modes:

ModeBehavior for a gesture that starts in a scrollable
noneThe scrollable keeps the gesture; the sheet does not move.
initialThe sheet or scrollable is selected when the gesture begins, and that owner is kept until the finger lifts.
handoffUses the same initial selection but can transfer between the sheet and scrollable at their boundary without lifting.

For example, with expansion set to handoff, dragging up first moves the sheet to its largest detent and then scrolls the content. With collapse set to handoff, dragging down first scrolls the content to its visual start and then moves the sheet to a smaller detent. Releasing before the sheet reaches the boundary does not fling the content.

Pass a string to use the same mode in both directions:

<BottomSheet scrollCoordination="none">{/* ... */}</BottomSheet>

Or pass an object to configure the directions separately:

<BottomSheet scrollCoordination={{ expand: 'initial', collapse: 'handoff' }}>
{/* ... */}
</BottomSheet>

Gestures that start outside a scrollable are unaffected and continue to move the sheet normally.

On iOS and Android, a drag that starts diagonally inside a vertical scrollable is resolved like a vertical one: the sheet takes any start the scrollable would have accepted as a scroll, so a shallow angle cannot scroll content the sheet would otherwise move, although on iOS the content may move briefly before the sheet takes the drag. A horizontal scrollable under the touch keeps sideways gestures as before. On every platform, a vertical start in a direction the sheet cannot move, such as downward at its lowest draggable detent, stays with the scrollable. On iOS, if the drag reverses into a handoff direction, the sheet can take over without lifting once any bounce or refresh pull has scrolled back; an initial direction keeps the original owner.

Platform support​

Nested and inverted vertical lists are resolved at their visual edge, while horizontal lists keep their horizontal gesture handling. Native bounce and refresh controls take over after the sheet reaches the boundary in their direction.

On iOS, handoff works with UIScrollView subclasses. On Android, handoff requires an Android nested-scrolling child. React Native’s vertical ScrollView, FlatList, and SectionList use a native Android ScrollView. For the active touch gesture, the sheet temporarily turns on that view’s nested-scrolling participation if needed, then restores its previous setting. This does not change scrollEnabled. Third-party components work when they implement the Android nested-scrolling-child contract and have nested scrolling enabled. Components such as WebView and bespoke scrollers that do not participate in nested scrolling use initial behavior as a fallback and cannot switch consumers in the middle of a gesture.

On the web, handoff works with any ancestor element of the touch that has overflow-y: auto or scroll and actually overflows. Inverted lists are recognized by a negative vertical scale, as React Native Web renders them. Mouse drags that hand off to content scroll it directly, since browsers don't drag-scroll with a mouse. A start that is at least as horizontal as it is vertical is always left to the browser, inside or outside a scrollable.