Skip to main content

Overview

Gestures transform animations from predefined sequences into interactive experiences. Users can drag screens to control progress, snap to intermediate points, and drive animations in real-time.

Basic Gesture Setup

Enable gestures on a stack with these props:
<Transition.Stack
  gestureEnabled
  gestureDirection="vertical"
  screenStyleInterpolator={screenStyleInterpolator}
>
  {/* screens */}
</Transition.Stack>
Now users can drag the screen vertically to:
  • Move between snap points (if configured)
  • Dismiss the screen (progress 1–2)
  • Interact naturally with the transition

Gesture Options

OptionTypeDefaultDescription
gestureEnabledbooleanfalseEnable gesture interaction
gestureDirection”horizontal” | “vertical""horizontal”Drag axis
gestureActivationAreaobjectscreen boundsArea where gesture starts
gestureResponseDistanceobject25Distance to activate gesture
gestureVelocityImpactnumber0.3Velocity influence on progress
gestureDrivesProgressbooleantrueDrag affects animation progress
snapVelocityImpactnumber0.1Velocity influence on snap point selection
gestureSnapLockedbooleanfalseLock to snap points (no intermediate states)
gestureReleaseVelocityScalenumber1Multiplier for release velocity
gestureReleaseVelocityMaxnumberInfinityCap for release velocity (px/s)
backdropBehavior”static” | “interactive""static”Tap backdrop to dismiss
backdropComponentReact.ComponentViewCustom backdrop renderer
sheetScrollGestureBehavior”expand-and-collapse” | “collapse-only""expand-and-collapse”Sheet scroll boundary behavior

Gesture Activation Area

Define where gestures can start:
<Transition.Stack
  gestureEnabled
  gestureActivationArea={{
    top: 0,        // Distance from top edge
    bottom: 0,     // Distance from bottom edge
    left: 0,       // Distance from left edge
    right: 0,      // Distance from right edge
  }}
>
  {/* screens */}
</Transition.Stack>
Useful to exclude buttons or controls that shouldn’t be swipeable.

Gesture Response Distance

How far a user must drag before the gesture activates:
<Transition.Stack
  gestureEnabled
  gestureResponseDistance={{ vertical: 25 }}
>
  {/* screens */}
</Transition.Stack>
Larger values require more dragging to activate, reducing accidental gestures.

Velocity-Based Release

When the user releases the gesture, the gesture’s velocity influences animation:
<Transition.Stack
  gestureEnabled
  gestureVelocityImpact={0.3}         // How much velocity affects progress
  gestureReleaseVelocityScale={1.5}   // Multiply velocity by this
  gestureReleaseVelocityMax={2000}    // Cap velocity at 2000 px/s
  snapVelocityImpact={0.1}            // Velocity influence on snap selection
>
  {/* screens */}
</Transition.Stack>
High velocity can complete animations or select different snap points on release.

Interactive Backdrop

Allow users to dismiss by tapping the backdrop:
<Transition.Modal
  gestureEnabled
  backdropBehavior="interactive"
>
  {/* modal content */}
</Transition.Modal>
Or provide a custom backdrop component:
<Transition.Modal
  gestureEnabled
  backdropComponent={({ style, ...props }) => (
    <Animated.View
      {...props}
      style={[style, { backgroundColor: 'rgba(0, 0, 0, 0.8)' }]}
    />
  )}
>
  {/* modal content */}
</Transition.Modal>

Sheet Scroll Gesture Behavior

When a sheet contains a ScrollView, define how scrolling at boundaries works:
<Transition.Modal
  snapPoints={[0.4, 1]}
  sheetScrollGestureBehavior="expand-and-collapse"
  gestureEnabled
>
  <ScrollView>
    {/* content */}
  </ScrollView>
</Transition.Modal>

Expand and Collapse

With "expand-and-collapse", scrolling at the top of the sheet can expand it:
// When ScrollView is at top (scrollY = 0), dragging up expands the sheet
sheetScrollGestureBehavior="expand-and-collapse"

Collapse Only

With "collapse-only", the sheet can’t expand via scroll. Users must use dead space (area without scrollable content):
// Scroll doesn't expand; only dismiss/collapse via drag
sheetScrollGestureBehavior="collapse-only"

Snap-Locked Gestures

Force gestures to snap to defined points instead of resting at intermediate positions:
<Transition.Modal
  snapPoints={[0.4, 0.7, 1]}
  gestureSnapLocked
  gestureEnabled
>
  {/* content */}
</Transition.Modal>
Users can only drag to 0.4, 0.7, or 1.0—never rest between them.

Ancestor Targeting

Control which navigator level handles the gesture using ancestor targeting:
// Own the gesture at current level
useScreenGesture("self", {
  gestureEnabled: true,
  gestureDirection: "vertical",
});

// Inherit parent's gesture configuration
useScreenGesture("parent", {
  gestureEnabled: true,
});

// Use root navigator's gesture
useScreenGesture("root", {
  gestureEnabled: true,
});

// Target a specific ancestor level (2 = grandparent)
useScreenGesture({ ancestor: 2 }, {
  gestureEnabled: true,
});
Same targeting options work with useScreenAnimation():
useScreenAnimation("parent", {
  screenStyleInterpolator,
});
This is especially useful in complex navigation hierarchies where you need to coordinate gestures across multiple levels.

Manual Gesture Control

Use the useScreenGesture hook to set gesture options programmatically:
import { useScreenGesture } from 'react-native-screen-transitions';

function MyScreen() {
  useScreenGesture("self", {
    gestureEnabled: true,
    gestureDirection: "vertical",
    snapVelocityImpact: 0.2,
  });

  return (
    <View style={{ flex: 1 }}>
      {/* screen content */}
    </View>
  );
}

Gesture Ownership

In nested navigators, gestures are claimed per direction. See Gesture Ownership for detailed information on how ownership works and resolves conflicts. Key principles:
  • Per-direction ownership: Each axis direction (horizontal, vertical) has one owner
  • Child shadows parent: A child navigator’s gesture shadows the parent’s on the same axis
  • Snap point claims: Snap points claim ownership of their axis
  • ScrollView coordination: Scroll gestures yield to navigator gestures at boundaries

Simultaneous Gestures

Coordinate multiple gestures with simultaneousWithExternalGesture:
import { useSharedValue } from 'react-native-reanimated';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';

function MyScreen() {
  const offset = useSharedValue(0);

  const horizontalPan = Gesture.Pan()
    .onUpdate((e) => {
      offset.value = e.translationX;
    });

  return (
    <GestureDetector gesture={horizontalPan}>
      <View style={{ flex: 1 }}>
        {/* content */}
      </View>
    </GestureDetector>
  );
}
The screen’s vertical gesture and the custom horizontal gesture can run simultaneously without interfering.

Gesture Combinations

Example: A sheet with all gesture features enabled:
<Transition.Modal
  snapPoints={[0.3, 0.7, 1]}
  gestureEnabled
  gestureDirection="vertical"
  gestureResponseDistance={{ vertical: 20 }}
  gestureVelocityImpact={0.3}
  gestureReleaseVelocityScale={1.5}
  gestureReleaseVelocityMax={2000}
  snapVelocityImpact={0.15}
  sheetScrollGestureBehavior="expand-and-collapse"
  backdropBehavior="interactive"
  screenStyleInterpolator={sheetInterpolator}
>
  <ScrollView>
    {/* sheet content */}
  </ScrollView>
</Transition.Modal>
Users can:
  • Drag to any snap point
  • Swipe with velocity to overshoot or undershoot
  • Scroll content and expand the sheet at the top
  • Tap the backdrop to dismiss

Troubleshooting

Gesture Not Activating

Check these in order:
  1. gestureEnabled is true
  2. gestureResponseDistance isn’t too large
  3. gestureActivationArea includes your touch point
  4. Parent navigator isn’t shadowing the gesture (see Gesture Ownership)

Gesture Conflicts with Scroll

If ScrollView scroll and navigator gesture both respond, use sheetScrollGestureBehavior to define boundaries:
sheetScrollGestureBehavior="collapse-only" // Scroll doesn't expand

Release Velocity Too Aggressive

Reduce the multiplier:
gestureReleaseVelocityScale={0.5}  // Lower values = less velocity influence
gestureReleaseVelocityMax={1000}   // Cap maximum velocity

Next Steps