Core Concepts

Snap Points

Define real sheet detents, auto sizing, release targeting, and scroll handoff.

What Snap Points Do

TSX

1<Stack.Screen
2 name="sheet"
3 component={SheetScreen}
4 options={{
5 gestureEnabled: true,
6 gestureDirection: "vertical",
7 snapPoints: [0.4, 0.7, 1],
8 initialSnapIndex: 0,
9 }}
10/>

snapPoints turns a screen into a detented sheet or drawer. Each numeric value is a fraction of the screen size on the active gesture axis.

gestureEnabled controls whether the sheet can dismiss past its minimum detent. It does not disable snapping between non-dismiss detents.

Allowed Values

Pass snap points in ascending order. The supported values are:

  • fractions such as 0.4, 0.7, or 1
  • "auto" for intrinsic content sizing

Initial Detent

initialSnapIndex selects which detent the screen opens at:

TSX

1<Stack.Screen
2 name="sheet"
3 component={SheetScreen}
4 options={{
5 gestureEnabled: true,
6 gestureDirection: "vertical",
7 snapPoints: [0.25, 0.5, 0.75, 1],
8 initialSnapIndex: 1,
9 }}
10/>

This screen opens at the second detent, 0.5.

Auto Snap Points

"auto" measures the content and resolves that detent from the intrinsic content size:

TSX

1<Stack.Screen
2 name="composer"
3 component={ComposerScreen}
4 options={{
5 gestureEnabled: true,
6 gestureDirection: "vertical",
7 snapPoints: ["auto", 1],
8 initialSnapIndex: 0,
9 }}
10/>

Reading Target Snap State

The selected target detent lives on current.snapIndex:

TSX

1screenStyleInterpolator: ({ current }) => {
2 "worklet";
3
4 return {
5 content: {
6 style: {
7 borderTopLeftRadius: interpolate(
8 current.snapIndex,
9 [0, 1, 2],
10 [28, 20, 0],
11 "clamp"
12 ),
13 borderTopRightRadius: interpolate(
14 current.snapIndex,
15 [0, 1, 2],
16 [28, 20, 0],
17 "clamp"
18 ),
19 },
20 },
21 };
22};

current.snapIndex updates when the target changes, such as initial mount, snapTo(), or gesture release. Use current.animatedSnapIndex when the visual state should follow the user's finger between detents.

Release Targeting

When the user releases the gesture, the library projects the current progress with the release velocity, then snaps to the target zone that projected value lands in.

These options shape that behavior:

TSX

1options={{
2 snapPoints: [0.3, 0.7, 1],
3 gestureSnapVelocityImpact: 0.15,
4 gestureReleaseVelocityScale: 1.2,
5}}
  • gestureSnapVelocityImpact changes how much the release velocity nudges the chosen detent
  • gestureReleaseVelocityScale changes the post-release spring energy

gestureReleaseVelocityScale affects how the sheet settles after the target is chosen. It does not replace gestureSnapVelocityImpact.

Snap Locking

gestureSnapLocked locks gesture-driven movement to the current detent:

TSX

1options={{
2 gestureEnabled: true,
3 gestureDirection: "vertical",
4 snapPoints: [0.25, 0.5, 0.75, 1],
5 initialSnapIndex: 1,
6 gestureSnapLocked: true,
7}}

With locking enabled, users cannot drag between detents. If dismissal is still enabled, swipe-to-dismiss remains available.

Because this is a screen option, you can toggle it at runtime with navigation.setOptions({ gestureSnapLocked: true }).

Scroll Handoff

If a snap sheet contains scrollable content, use the transition-aware scrollables and choose a handoff mode with sheetScrollGestureBehavior:

TSX

1function SheetScreen() {
2 return (
3 <Transition.ScrollView>
4 <LongContent />
5 </Transition.ScrollView>
6 );
7}
8
9<Stack.Screen
10 name="sheet"
11 component={SheetScreen}
12 options={{
13 gestureEnabled: true,
14 gestureDirection: "vertical",
15 snapPoints: [0.4, 0.7, 1],
16 sheetScrollGestureBehavior: "collapse-only",
17 }}
18/>

Modes:

  • "expand-and-collapse": boundary drags from scroll content can expand and collapse the sheet
  • "collapse-only": boundary drags from scroll content can collapse, but expansion must start from dead space outside the scrollable area

The older expandViaScrollView boolean still maps to these modes, but it is deprecated.

Axis And Pinch Rules

Snap sheets can be driven by one or more pan axes and by pinch:

  • use vertical or vertical-inverted for bottom or top sheets
  • use horizontal or horizontal-inverted for drawers
  • use pinch-in or pinch-out for two-finger detent changes

When you pass an array, the first direction on each pan axis controls that axis's collapse polarity. For pinch, the first pinch direction controls collapse and the opposite pinch direction expands.

Configured pan and pinch gestures can track at the same time. The current active gesture owns release targeting, so pan can hand off to pinch or pinch can hand off to pan during the same interaction.

TSX

1<Stack.Screen
2 name="sheet"
3 component={SheetScreen}
4 options={{
5 gestureEnabled: true,
6 gestureDirection: ["pinch-out", "horizontal", "vertical-inverted"],
7 snapPoints: [0.45, 0.72, 1],
8 }}
9/>

In this example, pinch-out, dragging right, or dragging up collapses the sheet. Pinch-in, dragging left, or dragging down expands it.

Pan and pinch can track during the same interaction. The gesture that is active at release owns the snap or dismiss decision, while companion gesture values remain available for animation.

Reading Live And Target Detents

Use current.animatedSnapIndex when the UI should follow the live gesture position, including fractional values between detents:

TSX

1screenStyleInterpolator: ({ current }) => {
2 "worklet";
3
4 return {
5 content: {
6 style: {
7 borderRadius: interpolate(
8 current.animatedSnapIndex,
9 [0, 1, 2],
10 [28, 16, 0],
11 "clamp"
12 ),
13 },
14 },
15 };
16};

Use current.snapIndex when you need the current target detent after mount, snapTo(), or gesture release.