Skip to main content

Overview

Gestures allow users to dismiss screens by swiping in any direction. The library provides fine-grained control over gesture activation, response distance, and velocity handling.

Basic Configuration

Enable swipe-to-dismiss with two properties:
options={{
  gestureEnabled: true,
  gestureDirection: "vertical",
  ...Transition.Presets.SlideFromBottom(),
}}

Gesture Direction

Control which swipe directions trigger dismissal:
gestureDirection: "horizontal"          // swipe left to dismiss

Configuration Options

gestureEnabled
boolean
default:"false"
Enable swipe-to-dismiss gestures. For screens with snap points, setting this to false only blocks dismiss-to-0; snapping between non-dismiss points remains available.
gestureDirection
GestureDirection | GestureDirection[]
Direction(s) for swipe gesture. Can be a single direction or an array of directions.
gestureActivationArea
ActivationArea | SideActivation
default:"'screen'"
Where the gesture can start. Use "edge" for edge-only activation or "screen" for anywhere. Can also be configured per-side.
gestureResponseDistance
number
Pixel threshold that must be exceeded for the gesture to activate throughout the screen.
gestureVelocityImpact
number
How much the final swipe velocity affects the dismiss decision. Higher values make quick swipes more likely to dismiss.
gestureDrivesProgress
boolean
default:true
Whether the gesture directly controls animation progress. Set to false for gestures that only trigger dismissal without driving the animation.
snapVelocityImpact
number
How much velocity affects snap point targeting. Lower values (iOS-like) make snapping more deliberate; higher values make it more responsive to flicks.

Gesture Activation Area

Simple Configuration

gestureActivationArea: "edge"  // Only from screen edges

Per-Side Configuration

Configure different activation areas for each edge:
gestureActivationArea: {
  left: "edge",      // Swipe from left edge only
  right: "screen",   // Swipe from anywhere when going right
  top: "edge",       // Swipe from top edge only
  bottom: "screen",  // Swipe from anywhere when going down
}
Per-side configuration is useful for creating iOS-style back gestures (left edge only) while allowing full-screen swipes in other directions.

Working with ScrollViews

Use transition-aware scrollable components to ensure gestures work correctly:
import Transition from "react-native-screen-transitions";

function MyScreen() {
  return (
    <Transition.ScrollView>
      {/* Your content */}
    </Transition.ScrollView>
  );
}

Available Components

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

<Transition.FlatList 
  data={items} 
  renderItem={renderItem}
/>

Scroll Gesture Rules

Gestures activate based on scroll position and direction:
Direction: "vertical" (swipe down to dismiss)
  • Only activates when scrolled to top
  • Prevents conflicts with scroll-up gesture

Expand Via ScrollView

For screens with snap points, control whether scrolling at the boundary can expand the sheet:
options={{
  snapPoints: [0.5, 1],
  expandViaScrollView: true,  // Swipe up at scroll top expands sheet
}}
expandViaScrollView
boolean
default:true
  • true: Apple Maps style - swiping up at scroll top expands the sheet
  • false: Instagram style - expand only works via deadspace (non-scrollable areas)
Collapse (swipe down at scroll top) always works regardless of this setting.

Advanced Gesture Control

Custom Gesture Coordination

Coordinate your own pan gestures with the navigation gesture:
import { useScreenGesture } from "react-native-screen-transitions";
import { Gesture, GestureDetector } from "react-native-gesture-handler";

function MyScreen() {
  const screenGesture = useScreenGesture();

  const myPanGesture = Gesture.Pan()
    .simultaneousWithExternalGesture(screenGesture)
    .onUpdate((e) => {
      // Your gesture logic
    });

  return (
    <GestureDetector gesture={myPanGesture}>
      <View />
    </GestureDetector>
  );
}
Use useScreenGesture() when you have custom pan gestures that need to work alongside screen dismiss gestures.

Accessing Gesture Values

Gesture state is available in your interpolator:
screenStyleInterpolator: ({ current, layouts: { screen } }) => {
  "worklet";

  // Normalized values range from -1 to 1
  const dragX = interpolate(
    current.gesture.normalizedX,
    [-1, 0, 1],
    [-screen.width * 0.5, 0, screen.width * 0.5],
    "clamp",
  );

  const dragY = interpolate(
    current.gesture.normalizedY,
    [-1, 0, 1],
    [-screen.height * 0.5, 0, screen.height * 0.5],
    "clamp",
  );

  return {
    contentStyle: {
      transform: [
        { translateX: dragX },
        { translateY: dragY },
      ],
    },
  };
}

Gesture Values API

isDragging
number
Whether the user’s finger is on the screen: 0 (no) or 1 (yes)
x
number
Live horizontal translation of the gesture in pixels
y
number
Live vertical translation of the gesture in pixels
normalizedX
number
Normalized horizontal translation from -1 (left) to 1 (right)
normalizedY
number
Normalized vertical translation from -1 (up) to 1 (down)
isDismissing
number
Whether the screen is being dismissed: 0 (no) or 1 (yes)
direction
string | null
Initial direction that activated the gesture: "horizontal", "horizontal-inverted", "vertical", "vertical-inverted", or null

Examples

Multi-Directional Draggable Card

From the DraggableCard preset:
screenStyleInterpolator: ({ current, progress, layouts: { screen } }) => {
  "worklet";

  const scale = interpolate(progress, [0, 1, 2], [0, 1, 0.75]);

  const translateY = interpolate(
    current.gesture.normalizedY,
    [-1, 1],
    [-screen.height * 0.5, screen.height * 0.5],
    "clamp",
  );

  const translateX = interpolate(
    current.gesture.normalizedX,
    [-1, 1],
    [-screen.width * 0.5, screen.width * 0.5],
    "clamp",
  );

  return {
    contentStyle: {
      transform: [
        { scale },
        { translateY },
        { translateX },
      ],
    },
  };
}

Elastic Card with Backdrop

From the ElasticCard preset:
import { interpolateColor } from "react-native-reanimated";

screenStyleInterpolator: ({
  current,
  next,
  layouts: { screen },
  progress,
}) => {
  "worklet";

  const scale = interpolate(progress, [0, 1, 2], [0, 1, 0.8]);

  const maxElasticityX = screen.width * 0.5;
  const maxElasticityY = screen.height * 0.5;
  
  const translateX = interpolate(
    current.gesture.normalizedX,
    [-1, 0, 1],
    [-maxElasticityX, 0, maxElasticityX],
    "clamp",
  );

  const translateY = interpolate(
    current.gesture.normalizedY,
    [-1, 0, 1],
    [-maxElasticityY, 0, maxElasticityY],
    "clamp",
  );

  const overlayColor = interpolateColor(
    progress,
    [0, 1],
    ["rgba(0,0,0,0)", "rgba(0,0,0,0.5)"],
  );

  return {
    contentStyle: {
      transform: [{ scale }, { translateX }, { translateY }],
    },
    backdropStyle: {
      backgroundColor: !next ? overlayColor : "rgba(0,0,0,0)",
    },
  };
}

Backdrop Behavior

Control how touches interact with the backdrop area:
backdropBehavior
string
default:"'block'"
  • "block": Backdrop catches all touches (default)
  • "passthrough": Touches pass through to content behind
  • "dismiss": Tapping backdrop dismisses the screen
  • "collapse": Tapping backdrop collapses to next lower snap point, then dismisses
options={{
  gestureEnabled: true,
  backdropBehavior: "dismiss", // Tap outside to dismiss
}}
backdropBehavior works independently of gesture configuration. You can have tap-to-dismiss without swipe gestures, or vice versa.