Skip to main content

Overlays Overview

Overlays allow screens to appear on top of other screens while maintaining proper gesture and event handling. They’re used for modals, sheets, alerts, and popovers.

Creating an Overlay

Use Transition.Modal to present content as an overlay:
import { Transition } from 'react-native-screen-transitions';
import { Text, View } from 'react-native';

function RootNavigator() {
  return (
    <Transition.Stack>
      <Transition.Screen name="Home" component={HomeScreen} />

      <Transition.Modal
        name="Details"
        component={DetailsScreen}
        options={{
          snapPoints: [0.5, 1],
          gestureEnabled: true,
        }}
      />
    </Transition.Stack>
  );
}
The modal is stacked above the base stack and can be dismissed by gesture or navigation back.
AspectStackModal
PresentationPush/popOverlay
Z-indexSequentialAbove everything
Snap pointsNot applicableSupported
Back buttonReturns to prev screenDismisses
BackdropOptionalDefault
GestureFull-screen dragSwipe to dismiss

Snap Points for Overlays

Modal overlays typically use snap points for sheet-like behavior:
<Transition.Modal
  name="Sheet"
  component={SheetScreen}
  options={{
    snapPoints: [
      0.25,  // Minimized
      0.5,   // Half
      1,     // Fullscreen
    ],
    gestureEnabled: true,
    screenStyleInterpolator: sheetInterpolator,
  }}
/>

Animating Overlays

Define custom animations for overlays:
const overlayInterpolator = ({ progress }) => {
  "worklet";
  return {
    content: {
      style: {
        opacity: interpolate(progress, [0, 1], [0, 1]),
        transform: [
          {
            translateY: interpolate(progress, [0, 1], [800, 0]),
          },
        ],
      },
    },
    backdrop: {
      style: {
        opacity: interpolate(progress, [0, 1], [0, 0.7]),
      },
    },
  };
};

<Transition.Modal
  name="Overlay"
  component={OverlayScreen}
  options={{
    screenStyleInterpolator: overlayInterpolator,
  }}
/>

Multiple Overlays

Stack multiple overlays:
<Transition.Stack>
  <Transition.Screen name="Home" component={HomeScreen} />

  <Transition.Modal name="Modal1" component={Modal1} />
  <Transition.Modal name="Modal2" component={Modal2} />
  <Transition.Modal name="Modal3" component={Modal3} />
</Transition.Stack>
Only the topmost modal is visible. Navigate back to dismiss and reveal the previous one.

Interactive Backdrop

Allow users to dismiss overlays by tapping the backdrop:
<Transition.Modal
  name="Overlay"
  component={OverlayScreen}
  options={{
    backdropBehavior: "interactive",
    gestureEnabled: true,
  }}
/>
Users can:
  • Tap the backdrop to dismiss
  • Swipe/drag to dismiss
  • Interact with content

Custom Backdrop Component

Provide a custom backdrop renderer:
const CustomBackdrop = ({ style, ...props }) => (
  <Animated.View
    {...props}
    style={[
      style,
      {
        backgroundColor: "rgba(0, 0, 0, 0.8)",
        backdropFilter: "blur(10px)",
      },
    ]}
  />
);

<Transition.Modal
  name="Overlay"
  component={OverlayScreen}
  options={{
    backdropComponent: CustomBackdrop,
  }}
/>

Overlay Gestures

Control overlay gesture behavior:
<Transition.Modal
  name="Overlay"
  component={OverlayScreen}
  options={{
    gestureEnabled: true,
    gestureDirection: "vertical",
    gestureResponseDistance: { vertical: 50 },
    gestureDrivesProgress: true,
  }}
/>
Users can drag the overlay down to dismiss it.

Preventing Interaction Below

Set pointerEvents to prevent touches from reaching underlying screens:
const overlayInterpolator = ({ progress }) => {
  "worklet";
  return {
    content: {
      style: { opacity: 1 },
      props: {
        pointerEvents: progress > 0.5 ? "auto" : "none",
      },
    },
    backdrop: {
      style: { opacity: interpolate(progress, [0, 1], [0, 0.5]) },
      props: {
        pointerEvents: progress > 0.5 ? "auto" : "none",
      },
    },
  };
};

Overlay from Context Menu

Present an overlay triggered by context menu action:
import { useNavigation } from '@react-navigation/native';
import { showMessage } from 'react-native-flash-message';

function HomeScreen() {
  const navigation = useNavigation();

  return (
    <View>
      <Pressable
        onLongPress={() => {
          navigation.navigate('ContextMenu');
        }}
      >
        <Text>Long press for menu</Text>
      </Pressable>
    </View>
  );
}

// In root navigator
<Transition.Modal
  name="ContextMenu"
  component={ContextMenuOverlay}
  options={{
    snapPoints: [0.3],
    gestureEnabled: true,
  }}
/>

Alert-Style Overlay

Simple centered overlay with minimal snap point:
const alertInterpolator = ({ progress }) => {
  "worklet";
  return {
    content: {
      style: {
        opacity: interpolate(progress, [0, 1], [0, 1]),
        transform: [
          {
            scale: interpolate(progress, [0, 1], [0.8, 1]),
          },
        ],
      },
    },
    backdrop: {
      style: {
        opacity: interpolate(progress, [0, 1], [0, 0.8]),
      },
    },
  };
};

<Transition.Modal
  name="Alert"
  component={AlertScreen}
  options={{
    snapPoints: [1],  // Fullscreen/centered
    gestureEnabled: false,  // No swipe dismiss
    screenStyleInterpolator: alertInterpolator,
  }}
/>

Popover Overlay

Floating popover at specific position:
const popoverInterpolator = ({ progress }) => {
  "worklet";
  return {
    content: {
      style: {
        opacity: interpolate(progress, [0, 1], [0, 1]),
        transform: [
          {
            scale: interpolate(progress, [0, 1], [0.7, 1]),
          },
        ],
      },
    },
  };
};

<Transition.Modal
  name="Popover"
  component={PopoverScreen}
  options={{
    snapPoints: [0.2, 0.5],  // Compact floating popover
    screenStyleInterpolator: popoverInterpolator,
    backdropBehavior: "interactive",
  }}
/>

Overlay Nesting

Overlays can present other overlays:
<Transition.Stack>
  <Transition.Screen name="Home" component={HomeScreen} />

  {/* First overlay */}
  <Transition.Modal name="Modal1" component={Modal1} />

  {/* Second overlay, nested in first */}
  <Transition.Modal name="Modal2" component={Modal2} />
</Transition.Stack>
Navigate to Modal1, then to Modal2. Both are visible in the hierarchy.

Programmatic Navigation to Overlays

import { useNavigation } from '@react-navigation/native';

function HomeScreen() {
  const navigation = useNavigation();

  return (
    <Pressable onPress={() => navigation.navigate('Overlay')}>
      <Text>Open Overlay</Text>
    </Pressable>
  );
}

Dismissing Overlays

Several ways to dismiss an overlay:
import { useNavigation } from '@react-navigation/native';

function OverlayScreen() {
  const navigation = useNavigation();

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      {/* Method 1: useNavigation().goBack() */}
      <Pressable onPress={() => navigation.goBack()}>
        <Text>Close</Text>
      </Pressable>

      {/* Method 2: navigation.pop() */}
      <Pressable onPress={() => navigation.pop()}>
        <Text>Pop</Text>
      </Pressable>

      {/* Method 3: Swipe gesture (if enabled) */}
      <Text>Swipe down to dismiss</Text>
    </View>
  );
}

Overlay With Scroll

Combine overlays with scrollable content:
<Transition.Modal
  name="ScrollableOverlay"
  component={ScrollableOverlayScreen}
  options={{
    snapPoints: ["auto", 1],  // Auto-size to content
    sheetScrollGestureBehavior: "expand-and-collapse",
    gestureEnabled: true,
  }}
/>

function ScrollableOverlayScreen() {
  return (
    <Transition.ScrollView>
      <View style={{ padding: 20 }}>
        <Text>Scrollable content here</Text>
        {/* Long content that scrolls */}
      </View>
    </Transition.ScrollView>
  );
}

Overlay Transitions Timing

Control animation duration:
<Transition.Modal
  name="Overlay"
  component={OverlayScreen}
  options={{
    transitionSpec: {
      open: {
        animation: 'timing',
        config: { duration: 500 },
      },
      close: {
        animation: 'timing',
        config: { duration: 300 },
      },
    },
  }}
/>

Best Practices

  1. Use snap points for sheet-style overlays
  2. Enable gesture for better UX (unless modal alert)
  3. Interactive backdrop for dismiss affordance
  4. Limit nesting depth to 2-3 overlays max
  5. Test gesture conflicts with underlying screens
  6. Provide visual feedback for interactive state

Troubleshooting

Overlay Not Appearing

Check:
  1. Modal is defined in navigator
  2. Navigation.navigate() uses correct screen name
  3. Modal is not conditionally hidden

Gestures Not Working

Ensure:
  1. gestureEnabled is true
  2. gestureDirection matches desired axis
  3. pointerEvents not blocking interaction

Backdrop Not Dismissing

Verify:
  1. backdropBehavior: "interactive"
  2. Backdrop onPress handler exists
  3. pointerEvents allows touches

Next Steps