Skip to main content
Expo Router integration is just React Navigation integration through withLayoutContext().

Create A Blank Stack Wrapper

// app/_layout.tsx
import "react-native-reanimated";
import type {
  ParamListBase,
  StackNavigationState,
} from "@react-navigation/native";
import { withLayoutContext } from "expo-router";
import {
  createBlankStackNavigator,
  type BlankStackNavigationEventMap,
  type BlankStackNavigationOptions,
} from "react-native-screen-transitions/blank-stack";

const { Navigator } = createBlankStackNavigator();

export const Stack = withLayoutContext<
  BlankStackNavigationOptions,
  typeof Navigator,
  StackNavigationState<ParamListBase>,
  BlankStackNavigationEventMap
>(Navigator);

export default function RootLayout() {
  return <Stack screenOptions={{ headerShown: false }} />;
}

Add Screen Options

Route files can still export screen options:
// app/details.tsx
import { View, Text } from "react-native";
import Transition from "react-native-screen-transitions";

export default function Details() {
  return (
    <View style={{ flex: 1, backgroundColor: "#fff" }}>
      <Text>Details Screen</Text>
    </View>
  );
}

export const options = {
  ...Transition.Presets.SlideFromBottom(),
  gestureEnabled: true,
  gestureDirection: "vertical",
};

Group Configuration

Use Stack.Group when you want one set of options for multiple routes:
// app/details/_layout.tsx
import { interpolate } from "react-native-reanimated";
import { Stack } from "@/_layout";

export default function DetailsLayout() {
  return (
    <Stack.Group
      screenOptions={{
        gestureEnabled: true,
        gestureDirection: "horizontal",
        screenStyleInterpolator: ({
          progress,
          layouts: {
            screen: { width },
          },
        }) => {
          "worklet";
          return {
            contentStyle: {
              transform: [
                {
                  translateX: interpolate(progress, [0, 1, 2], [width, 0, -width]),
                },
              ],
            },
          };
        },
      }}
    >
      <Stack.Screen name="[id]" />
    </Stack.Group>
  );
}

Shared Elements With Router

The 3.3 path still uses sharedBoundTag:
// app/index.tsx
import { router } from "expo-router";
import Transition from "react-native-screen-transitions";

<Transition.Pressable
  sharedBoundTag="photo"
  onPress={() => router.push("/photo")}
>
  <Image source={{ uri: "..." }} style={{ width: 100, height: 100 }} />
</Transition.Pressable>
// app/photo.tsx
import Transition from "react-native-screen-transitions";

export default function PhotoScreen() {
  return (
    <Transition.View sharedBoundTag="photo">
      <Image source={{ uri: "..." }} style={{ width: "100%", height: 300 }} />
    </Transition.View>
  );
}

export const options = {
  ...Transition.Presets.SharedIGImage({
    sharedBoundTag: "photo",
  }),
};

Notes

  • import "react-native-reanimated" at the top of your app entry
  • use Expo prebuild when you need native masked-view flows
  • keep custom interpolators in options exports or layout groups

Next Steps