Skip to main content

Snap Points Overview

Snap points define heights or positions where a sheet can rest when dragged. Users can snap between these points by dragging or using velocity.
<Transition.Modal
  snapPoints={[0.4, 0.7, 1]}
  gestureEnabled
  screenStyleInterpolator={sheetInterpolator}
>
  {/* sheet content */}
</Transition.Modal>
This creates a sheet with three resting positions:
  • 40% of screen height
  • 70% of screen height
  • 100% of screen height (fullscreen)

Snap Point Values

Snap points can be specified as:
FormatExampleMeaning
Number (0–1)0.550% of screen height
”auto” (NEW)"auto"Intrinsic content height
Pixel value300Absolute pixels (less common)

Basic Snap Points

Fixed percentage-based snap points:
<Transition.Modal
  snapPoints={[
    0.25,  // Quarter screen
    0.5,   // Half screen
    1,     // Fullscreen
  ]}
  gestureEnabled
  screenStyleInterpolator={({ progress }) => ({
    content: {
      style: {
        transform: [
          {
            translateY: interpolate(progress, [0, 1], [600, 0]),
          },
        ],
      },
    },
  })}
>
  {/* content */}
</Transition.Modal>

Auto Snap Points (NEW in 3.4)

Use "auto" to size a snap point to the content’s intrinsic height:
<Transition.Modal
  snapPoints={["auto", 1]}
  screenStyleInterpolator={({ progress, layouts }) => ({
    content: {
      style: {
        transform: [
          {
            translateY: interpolate(
              progress,
              [0, 1, 2],
              [layouts.content?.height ?? 300, 0, -100]
            ),
          },
        ],
      },
    },
  })}
>
  <View>
    <Text>This content automatically sizes the first snap point.</Text>
    <Button title="Action" onPress={() => {}} />
  </View>
</Transition.Modal>
The sheet measures its content height and uses that as the first snap point. Perfect for dynamic content like forms or lists.

Accessing Content Dimensions

In your screenStyleInterpolator, access the measured content height:
const sheetInterpolator = ({ progress, layouts }) => {
  "worklet";
  const contentHeight = layouts.content?.height ?? 300;

  return {
    content: {
      style: {
        transform: [
          {
            translateY: interpolate(
              progress,
              [0, 1, 2],
              [contentHeight, 0, -100]
            ),
          },
        ],
      },
    },
  };
};

Sheet Scroll Gesture Behavior

Control how ScrollView gestures interact with snap points:

Expand and Collapse (Default)

With "expand-and-collapse", users can expand the sheet by scrolling up when at the top:
<Transition.Modal
  snapPoints={[0.4, 1]}
  sheetScrollGestureBehavior="expand-and-collapse"
  gestureEnabled
>
  <ScrollView>
    <LongContent />
  </ScrollView>
</Transition.Modal>
Behavior:
  • Scroll down anywhere: ScrollView scrolls
  • At top (scrollY = 0), drag up: Sheet expands to next snap point
  • Below last snap point: Sheet snaps back up
  • Swipe down: Sheet collapses

Collapse Only

With "collapse-only", the sheet cannot expand via scroll. Users must drag from dead space:
<Transition.Modal
  snapPoints={[0.4, 1]}
  sheetScrollGestureBehavior="collapse-only"
  gestureEnabled
>
  <ScrollView>
    <LongContent />
  </ScrollView>
</Transition.Modal>
Behavior:
  • Scroll works anywhere: ScrollView has priority
  • At top, drag up: No effect (use expand-and-collapse for this)
  • Need to expand? Add a header or dead space above content
  • Drag from header or outside ScrollView to expand

Multiple Snap Points

Create complex sheet behaviors with multiple snap points:
const customInterpolator = ({ progress, layouts }) => {
  "worklet";

  return {
    content: {
      style: {
        transform: [
          {
            translateY: interpolate(
              progress,
              [0, 0.5, 1, 1.5, 2],
              [550, 300, 0, -100, -150]
            ),
          },
        ],
      },
    },
    backdrop: {
      style: {
        opacity: interpolate(
          progress,
          [0, 0.5, 1],
          [0, 0.3, 0.7]
        ),
      },
    },
  };
};

<Transition.Modal
  snapPoints={[0.5, 0.75, 1]}
  screenStyleInterpolator={customInterpolator}
  gestureEnabled
>
  {/* content */}
</Transition.Modal>

Velocity-Based Snap Selection

When users release a gesture with velocity, the snap point selection considers momentum:
<Transition.Modal
  snapPoints={[0.3, 0.7, 1]}
  snapVelocityImpact={0.15}  // Low value = snap point is primary
  gestureReleaseVelocityScale={1.2}
  gestureReleaseVelocityMax={2000}
  gestureEnabled
>
  {/* content */}
</Transition.Modal>
With high velocity, the animation may overshoot and land at a different snap point than closest position.

Snap Locked Gestures

Force snapping to exact points—no intermediate resting 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. Releasing the gesture snaps to the nearest point.

Programmatic Snap Control

Use the snap controller to programmatically move between snap points:
import { useSnapController } from 'react-native-screen-transitions';

function SheetContent() {
  const snap = useSnapController();

  return (
    <View style={{ flex: 1 }}>
      <Button
        title="Expand"
        onPress={() => snap.animateTo(1)}
      />
      <Button
        title="Collapse"
        onPress={() => snap.animateTo(0.4)}
      />
    </View>
  );
}

Combining with Shared Elements

Snap points work well with shared element animations:
const sheetInterpolator = ({ progress, bounds, layouts }) => {
  "worklet";
  const contentHeight = layouts.content?.height ?? 400;

  return {
    content: {
      style: {
        opacity: interpolate(progress, [0, 1], [0, 1]),
        transform: [
          {
            translateY: interpolate(
              progress,
              [0, 1, 2],
              [contentHeight, 0, -100]
            ),
          },
        ],
      },
    },
    "hero": {
      style: {
        borderRadius: interpolate(progress, [0, 1], [12, 0]),
      },
    },
  };
};

<Transition.Modal
  snapPoints={["auto", 1]}
  screenStyleInterpolator={sheetInterpolator}
  gestureEnabled
>
  <Transition.Boundary.View id="hero">
    <Image {...} />
  </Transition.Boundary.View>
  {/* rest of content */}
</Transition.Modal>

Snap Point Edge Cases

Very Tall Content

If content is taller than screen, use "auto" with a second snap point:
snapPoints={["auto", 1]}  // First auto, then fullscreen

Nested Snap Points

A sheet inside a sheet can have its own snap points:
<Transition.Modal snapPoints={[0.5, 1]}>
  {/* Outer sheet */}
  <Transition.Modal snapPoints={[0.3, 0.8]}>
    {/* Inner sheet—claims vertical gesture, outer can't control it */}
  </Transition.Modal>
</Transition.Modal>
The inner sheet shadows the outer on the vertical axis (see Gesture Ownership).

Performance Optimization

Avoid Too Many Snap Points

More snap points = more interpolation complexity:
// ✅ Good: Few clear snap points
snapPoints={[0.3, 0.7, 1]}

// ❌ Avoid: Too many points
snapPoints={[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]}

Use “auto” Wisely

Auto sizing requires layout measurement. For simple fixed heights, use numbers:
// ✅ Simple, fast
snapPoints={[0.5, 1]}

// ✅ Dynamic sizing
snapPoints={["auto", 1]}

// ❌ Multiple autos (unnecessary overhead)
snapPoints={["auto", "auto"]}

Troubleshooting

Snap Points Not Working

Check:
  1. gestureEnabled is true
  2. Interpolator uses snap point positions in the transform
  3. ScrollView scroll doesn’t prevent snapping (check sheetScrollGestureBehavior)

Content Doesn’t Expand

If using "auto" and content height is 0:
// Measure content explicitly
if (!layouts.content?.height) {
  return "defer";  // Wait for measurement
}

Velocity Too Strong

Reduce velocity impact:
snapVelocityImpact={0.05}  // Less velocity influence
gestureReleaseVelocityScale={0.5}  // Lower velocity multiplier

Next Steps