Skip to main content

Overview

When using gestures with scrollable content, you need to coordinate between scroll gestures and navigation gestures. react-native-screen-transitions provides transition-aware scrollable components that handle this coordination automatically.

Transition-Aware Components

Use these components instead of standard React Native scrollables:
import Transition from "react-native-screen-transitions";

// ScrollView
<Transition.ScrollView>
  {/* content */}
</Transition.ScrollView>

// FlatList
<Transition.FlatList
  data={items}
  renderItem={({ item }) => <Item data={item} />}
/>
These components accept all the same props as their React Native counterparts.

Gesture Rules with ScrollViews

The navigation gesture behavior depends on the gestureDirection and scroll position:

Vertical Gestures

  • At top of scroll: Navigation gesture activates (dismiss/snap)
  • Scrolled into content: Normal scroll behavior
  • At bottom: Only scroll behavior
<Stack.Screen
  name="Sheet"
  options={{
    gestureEnabled: true,
    gestureDirection: "vertical",
    ...Transition.Presets.SlideFromBottom(),
  }}
/>
Example: Bottom sheet that dismisses when swiping down from the top.
  • At bottom of scroll: Navigation gesture activates
  • Scrolled into content: Normal scroll behavior
  • At top: Only scroll behavior
<Stack.Screen
  name="TopSheet"
  options={{
    gestureEnabled: true,
    gestureDirection: "vertical-inverted",
    ...Transition.Presets.SlideFromTop(),
  }}
/>
Example: Top sheet that dismisses when swiping up from the bottom.

Horizontal Gestures

  • At left/right edge: Navigation gesture activates
  • Scrolled into content: Normal scroll behavior
<Stack.Screen
  name="Detail"
  options={{
    gestureEnabled: true,
    gestureDirection: "horizontal",
  }}
/>
Example: Standard iOS-style back gesture from left edge.

Boundary Behavior

When the user reaches a scroll boundary (top/bottom/edge), the gesture system automatically coordinates between scrolling and navigation.

Default Behavior

import { View } from "react-native";
import Transition from "react-native-screen-transitions";

function BottomSheet() {
  return (
    <View style={{ flex: 1 }}>
      <View style={{ padding: 20 }}>
        <Text>Header (deadspace)</Text>
      </View>

      <Transition.ScrollView>
        <Text>Scrollable content...</Text>
        {/* Long content */}
      </Transition.ScrollView>
    </View>
  );
}
  • Swipe down at top: Dismisses or collapses snap point
  • Swipe up in deadspace: Expands to next snap point (if snap points enabled)
  • Swipe within scroll: Normal scroll behavior
Deadspace refers to non-scrollable areas like headers or footers outside the ScrollView.

Snap Points with ScrollViews

With snap points enabled, ScrollView gestures can control snap expansion/collapse:
<Stack.Screen
  name="Sheet"
  options={{
    gestureEnabled: true,
    gestureDirection: "vertical",
    snapPoints: [0.5, 1],
    expandViaScrollView: true, // default
    ...Transition.Presets.SlideFromBottom(),
  }}
/>

Expand Via ScrollView

The expandViaScrollView option controls snap behavior at scroll boundaries:
options={{
  snapPoints: [0.5, 1],
  expandViaScrollView: true,
}}

// Behavior:
// - At top boundary: Swipe up expands, swipe down collapses/dismisses
// - In content: Normal scroll
When expandViaScrollView: false, users can only expand the sheet by swiping in deadspace areas (outside the ScrollView) or via programmatic control with snapTo().

Example: Bottom Sheet with ScrollView

Complete example showing proper ScrollView integration:
import { View, Text } from "react-native";
import Transition from "react-native-screen-transitions";

export default function BottomSheetScreen() {
  return (
    <View style={{ flex: 1, backgroundColor: "white" }}>
      {/* Header - Deadspace */}
      <View style={{ padding: 20, borderBottomWidth: 1, borderColor: "#ccc" }}>
        <View
          style={{
            width: 40,
            height: 4,
            backgroundColor: "#ccc",
            borderRadius: 2,
            alignSelf: "center",
            marginBottom: 10,
          }}
        />
        <Text style={{ fontSize: 20, fontWeight: "bold" }}>Details</Text>
      </View>

      {/* Scrollable Content */}
      <Transition.ScrollView style={{ flex: 1 }}>
        <View style={{ padding: 20 }}>
          {Array.from({ length: 50 }).map((_, i) => (
            <Text key={i} style={{ marginVertical: 10 }}>
              Item {i + 1}
            </Text>
          ))}
        </View>
      </Transition.ScrollView>
    </View>
  );
}
// Layout configuration
<Stack.Screen
  name="bottomSheet"
  options={{
    gestureEnabled: true,
    gestureDirection: "vertical",
    snapPoints: [0.5, 1],
    initialSnapIndex: 0,
    backdropBehavior: "dismiss",
    expandViaScrollView: true,
    ...Transition.Presets.SlideFromBottom(),
  }}
/>

FlatList Support

FlatList works the same way as ScrollView:
import Transition from "react-native-screen-transitions";

function ListScreen() {
  const data = Array.from({ length: 100 }, (_, i) => ({ id: i, title: `Item ${i}` }));

  return (
    <Transition.FlatList
      data={data}
      renderItem={({ item }) => (
        <View style={{ padding: 20 }}>
          <Text>{item.title}</Text>
        </View>
      )}
      keyExtractor={(item) => item.id.toString()}
    />
  );
}
Both Transition.ScrollView and Transition.FlatList support all standard props including ref, onScroll, contentContainerStyle, etc.
  • Gestures - Complete gesture configuration guide
  • Snap Points - Multi-stop sheet behavior
  • Presets - Built-in gesture-enabled transitions