This guide will walk you through creating a stack navigator with custom transitions. You’ll learn how to use presets, customize animations, and add gesture support.
Create a Basic Stack
Start by creating a stack navigator using the Blank Stack (recommended for most apps):
React Navigation
import { createBlankStackNavigator } from "react-native-screen-transitions/blank-stack" ;
import Transition from "react-native-screen-transitions" ;
import { View , Text , Button } from "react-native" ;
const Stack = createBlankStackNavigator ();
function HomeScreen ({ navigation }) {
return (
< View style = { { flex: 1 , alignItems: 'center' , justifyContent: 'center' } } >
< Text > Home Screen </ Text >
< Button
title = "Go to Detail"
onPress = { () => navigation . navigate ( 'Detail' ) }
/>
</ View >
);
}
function DetailScreen () {
return (
< View style = { { flex: 1 , alignItems: 'center' , justifyContent: 'center' } } >
< Text > Detail Screen </ Text >
</ View >
);
}
function App () {
return (
< Stack.Navigator >
< Stack.Screen name = "Home" component = { HomeScreen } />
< Stack.Screen
name = "Detail"
component = { DetailScreen }
options = { {
... Transition . Presets . SlideFromBottom (),
} }
/>
</ Stack.Navigator >
);
}
export default App ;
Expo Router
For Expo Router, set up the stack in your layout file:
Create the Stack component
Create a reusable Stack component using withLayoutContext: import { withLayoutContext } from "expo-router" ;
import {
createBlankStackNavigator ,
type BlankStackNavigationOptions ,
} from "react-native-screen-transitions/blank-stack" ;
const { Navigator } = createBlankStackNavigator ();
export const Stack = withLayoutContext <
BlankStackNavigationOptions ,
typeof Navigator
> ( Navigator );
Use the Stack in your layout
Import and use the Stack in your root layout: import Transition from "react-native-screen-transitions" ;
import { Stack } from "./stack" ;
export default function RootLayout () {
return (
< Stack >
< Stack.Screen name = "index" />
< Stack.Screen
name = "detail"
options = { {
... Transition . Presets . SlideFromBottom (),
} }
/>
</ Stack >
);
}
Create your screens
Create screen components in separate files: import { View , Text , Button } from "react-native" ;
import { router } from "expo-router" ;
export default function HomeScreen () {
return (
< View style = { { flex: 1 , alignItems: 'center' , justifyContent: 'center' } } >
< Text > Home Screen </ Text >
< Button
title = "Go to Detail"
onPress = { () => router . push ( '/detail' ) }
/>
</ View >
);
}
import { View , Text } from "react-native" ;
export default function DetailScreen () {
return (
< View style = { { flex: 1 , alignItems: 'center' , justifyContent: 'center' } } >
< Text > Detail Screen </ Text >
</ View >
);
}
Use Built-in Presets
The library includes several ready-made transition presets. Apply them using the spread operator:
import Transition from "react-native-screen-transitions" ;
< Stack.Screen
name = "Detail"
component = { DetailScreen }
options = { {
... Transition . Presets . SlideFromBottom (),
} }
/>
Available Presets
Preset Description SlideFromTop()Slides in from the top of the screen SlideFromBottom()Slides in from bottom (modal-style) ZoomIn()Scales in with fade effect DraggableCard()Multi-directional drag with scaling ElasticCard()Elastic drag with overlay backdrop SharedIGImage({ sharedBoundTag })Instagram-style shared image transition SharedAppleMusic({ sharedBoundTag })Apple Music-style shared element SharedXImage({ sharedBoundTag })X (Twitter)-style image transition
Shared element presets require @react-native-masked-view/masked-view to be installed. See the Installation guide for setup instructions.
Add Gesture Support
Enable swipe-to-dismiss by adding gesture options:
< Stack.Screen
name = "Detail"
component = { DetailScreen }
options = { {
gestureEnabled: true ,
gestureDirection: "vertical" ,
... Transition . Presets . SlideFromBottom (),
} }
/>
Gesture Directions
Control which direction triggers the dismiss gesture:
// Swipe down to dismiss
gestureDirection : "vertical"
// Swipe up to dismiss
gestureDirection : "vertical-inverted"
// Swipe left to dismiss
gestureDirection : "horizontal"
// Swipe right to dismiss
gestureDirection : "horizontal-inverted"
// Any direction dismisses
gestureDirection : "bidirectional"
// Multiple directions
gestureDirection : [ "horizontal" , "vertical" ]
Gesture Activation Areas
Control where gestures can start:
// Only from screen edges (recommended)
gestureActivationArea : "edge"
// Anywhere on screen
gestureActivationArea : "screen"
// Per-side configuration
gestureActivationArea : {
left : "edge" ,
right : "screen" ,
top : "edge" ,
bottom : "screen" ,
}
Create Custom Animations
Define your own transitions using the screenStyleInterpolator function. Every screen has a progress value that animates from 0 → 1 → 2:
0 ─────────── 1 ─────────── 2
entering visible exiting
Simple Fade Transition
import { interpolate } from "react-native-reanimated" ;
< Stack.Screen
name = "Detail"
component = { DetailScreen }
options = { {
screenStyleInterpolator : ({ progress }) => {
"worklet" ;
return {
contentStyle: {
opacity: interpolate ( progress , [ 0 , 1 , 2 ], [ 0 , 1 , 0 ]),
},
};
},
} }
/>
Slide from Right
< Stack.Screen
name = "Detail"
component = { DetailScreen }
options = { {
screenStyleInterpolator : ({ progress , layouts : { screen } }) => {
"worklet" ;
return {
contentStyle: {
transform: [{
translateX: interpolate (
progress ,
[ 0 , 1 , 2 ],
[ screen . width , 0 , - screen . width * 0.3 ]
),
}],
},
};
},
} }
/>
Zoom with Fade
< Stack.Screen
name = "Detail"
component = { DetailScreen }
options = { {
screenStyleInterpolator : ({ progress }) => {
"worklet" ;
return {
contentStyle: {
opacity: interpolate ( progress , [ 0 , 1 , 2 ], [ 0 , 1 , 0 ]),
transform: [{
scale: interpolate ( progress , [ 0 , 1 , 2 ], [ 0.9 , 1 , 0.9 ]),
}],
},
};
},
} }
/>
Always include the "worklet"; directive at the start of your interpolator function. This tells Reanimated to run the code on the UI thread for optimal performance.
Control Animation Timing
Customize the animation speed using spring configurations:
< Stack.Screen
name = "Detail"
component = { DetailScreen }
options = { {
screenStyleInterpolator: myInterpolator ,
transitionSpec: {
open: { stiffness: 1000 , damping: 500 , mass: 3 }, // Screen enters
close: { stiffness: 1000 , damping: 500 , mass: 3 }, // Screen exits
},
} }
/>
Spring Configuration Options
Property Description Default stiffnessSpring stiffness (higher = faster) 1000 dampingSpring damping (higher = less bounce) 500 massSpring mass (higher = slower) 3
When using ScrollViews with gestures, use the transition-aware components:
import Transition from "react-native-screen-transitions" ;
function DetailScreen () {
return (
< Transition.ScrollView >
< Text > Scrollable content... </ Text >
</ Transition.ScrollView >
);
}
Also available:
Transition.FlatList - For lists
Transition.View - For shared elements
Transition.Pressable - For shared element sources
These components automatically coordinate with the navigation gesture system. Vertical gestures only activate when scrolled to the top (or bottom for inverted gestures).
Complete Example
Here’s a complete example combining multiple features:
import { createBlankStackNavigator } from "react-native-screen-transitions/blank-stack" ;
import Transition from "react-native-screen-transitions" ;
import { View , Text , Button } from "react-native" ;
import { interpolate } from "react-native-reanimated" ;
const Stack = createBlankStackNavigator ();
function HomeScreen ({ navigation }) {
return (
< View style = { { flex: 1 , alignItems: 'center' , justifyContent: 'center' } } >
< Text style = { { fontSize: 24 , marginBottom: 20 } } > Home </ Text >
< Button
title = "Show Modal"
onPress = { () => navigation . navigate ( 'Modal' ) }
/>
</ View >
);
}
function ModalScreen () {
return (
< View style = { { flex: 1 , backgroundColor: 'white' } } >
< Transition.ScrollView
contentContainerStyle = { { padding: 20 } }
>
< Text style = { { fontSize: 24 , marginBottom: 20 } } > Modal Screen </ Text >
< Text > Swipe down to dismiss... </ Text >
{ /* More content */ }
</ Transition.ScrollView >
</ View >
);
}
function App () {
return (
< Stack.Navigator >
< Stack.Screen name = "Home" component = { HomeScreen } />
< Stack.Screen
name = "Modal"
component = { ModalScreen }
options = { {
gestureEnabled: true ,
gestureDirection: "vertical" ,
gestureActivationArea: "edge" ,
... Transition . Presets . SlideFromBottom (),
} }
/>
</ Stack.Navigator >
);
}
export default App ;
Next Steps
You’ve learned the basics! Explore more advanced features:
Shared Elements Create smooth element transitions between screens
Snap Points Build multi-stop bottom sheets and side panels
Presets Explore all built-in transition presets
Custom Animations Learn advanced animation techniques