Learn how to use the Stack Layout in Expo Router.
The Stack
Layout in Expo Router wraps the Native Stack Navigator from React Navigation, not to be confused with the legacy JS Stack Navigator.
app
_layout.js
index.js
detail.js
To create a Stack
layout with two screens as shown in the file structure above:
import { Stack } from 'expo-router/stack';
export default function Layout() {
return <Stack />;
}
Use the screenOptions
prop to configure the header bar.
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack
screenOptions={{
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
/>
);
}
You can use a layout's Screen component to configure the header bar dynamically from within the route. This is good for interactions that change the UI.
import { Link, Stack } from 'expo-router';
import { Image, Text, View } from 'react-native';
function LogoTitle() {
return (
<Image
style={{ width: 50, height: 50 }}
source={{ uri: 'https://reactnative.dev/img/tiny_logo.png' }}
/>
);
}
export default function Home() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Stack.Screen
options={{
// https://reactnavigation.org/docs/headers#setting-the-header-title
title: 'My home',
// https://reactnavigation.org/docs/headers#adjusting-header-styles
headerStyle: { backgroundColor: '#f4511e' },
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
// https://reactnavigation.org/docs/headers#replacing-the-title-with-a-custom-component
headerTitle: props => <LogoTitle {...props} />,
}}
/>
<Text>Home Screen</Text>
<Link href={{ pathname: 'details', params: { name: 'Bacon' } }}>Go to Details</Link>
</View>
);
}
You can use the imperative API router.setParams()
function to configure the route dynamically.
import { View, Text } from 'react-native';
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
export default function Details() {
const router = useRouter();
const params = useLocalSearchParams();
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Stack.Screen
options={{
title: params.name,
}}
/>
<Text
onPress={() => {
router.setParams({ name: 'Updated' });
}}>
Update the title
</Text>
</View>
);
}
As an alternative to the <Stack.Screen>
component, you can use navigation.setOptions()
to configure screen options from within the component.
import { Stack, useNavigation } from 'expo-router';
import { Text, View } from 'react-native';
export default function Home() {
const navigation = useNavigation();
React.useEffect(() => {
navigation.setOptions({ headerShown: false });
}, [navigation]);
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
</View>
);
}
With the following file structure:
app
_layout.js
home.js
You can use the <Stack.Screen name={routeName} />
component in the layout component route to statically configure screen options. This is useful for tab bars or drawers which need to have an icon defined ahead of time.
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack
// https://reactnavigation.org/docs/headers#sharing-common-options-across-screens
screenOptions={{
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}>
{/* Optionally configure static options outside the route. */}
<Stack.Screen name="home" options={{}} />
</Stack>
);
}
Use the <Stack.Screen />
component in the child route to dynamically configure options.
import { Button, Text, Image } from 'react-native';
import { Stack } from 'expo-router';
function LogoTitle() {
return (
<Image
style={{ width: 50, height: 50 }}
source={require('@expo/snack-static/react-native-logo.png')}
/>
);
}
export default function Home() {
const [count, setCount] = React.useState(0);
return (
<>
<Stack.Screen
options={{
headerTitle: props => <LogoTitle {...props} />,
headerRight: () => <Button onPress={() => setCount(c => c + 1)} title="Update count" />,
}}
/>
<Text>Count: {count}</Text>
</>
);
}
By default, the Stack
will deduplicate pages when pushing a route that is already in the stack. For example, if you push the same profile twice, the second push will be ignored. You can change the pushing behavior by providing a custom getId
function to the Stack.Screen
.
app
_layout.js
[user].js
You can use the <Stack.Screen name="[user]" getId={} />
component in the layout component route to modify the pushing behavior. In the following example, the getId
function pushes a new page every time the user navigates to a profile.
import { Stack } from 'expo-router';
export default function Layout() {
return (
<Stack>
<Stack.Screen
name="[user]"
getId={({ params }) => String(Date.now())}
/>
</Stack>
);
}
Expo Router uses the @react-navigation/native-stack
package for the <Stack />
layout. However, you can also use the JavaScript-powered @react-navigation/stack
library instead to create a custom layout component that wraps the @react-navigation/stack
library.
In the following example, JsStack
component is created using @react-navigation/stack
library:
import { ParamListBase, StackNavigationState } from '@react-navigation/native';
import {
createStackNavigator,
StackNavigationEventMap,
StackNavigationOptions,
} from '@react-navigation/stack';
import { withLayoutContext } from 'expo-router';
const { Navigator } = createStackNavigator();
export const JsStack = withLayoutContext<
StackNavigationOptions,
typeof Navigator,
StackNavigationState<ParamListBase>,
StackNavigationEventMap
>(Navigator);
You can then use the JsStack
component in your app.
import { JsStack } from '../layouts/js-stack';
export default function Layout() {
return (
<JsStack
screenOptions={
{
// Refer to the React Navigation docs https://reactnavigation.org/docs/stack-navigator
}
}
/>
);
}
For more information on available options, see @react-navigation/stack
documentation.
dismiss
actionDismissed the last screen in the closest stack. If the current screen is the only screen in the stack, then it will dismiss the entire stack.
You can optionally pass a positive number to dismiss up to that specified number of screens.
Dismiss is different to back
, as it targets the closest stack and not the current navigator. If you have nested navigators, calling dismiss
may take you back multiple screens.
import { Button, View, Text } from "react-native";
import { useRouter } from "expo-router";
export default function Settings() {
const router = useRouter();
const handleDismiss = (count) => {
router.dismiss(count)
};
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Button title="Go to first screen" onPress={() => dismiss(3)} />
</View>
);
}
dismissAll
actionTo return to the first screen in the closest stack stack. This is similar to popToTop
stack action.
The home
screen is the first screen, and the settings
is the last. To go from settings
to home
screen you'll have to go back to details
. However, using the dismissAll
action, you can go from settings
to home
and dismiss any screen in between.
import { Button, View, Text } from "react-native";
import { useRouter } from "expo-router";
export default function Settings() {
const router = useRouter();
const handleDismissAll = () => {
router.dismissAll()
};
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Button title="Go to first screen" onPress={handleDismissAll} />
</View>
);
}
canDismiss
To check if it possible to dismiss the current screen. Returns true
if the router is within a stack with more than one screen in it's history.
import { Button, View, Text } from "react-native";
import { useRouter } from "expo-router";
export default function Settings() {
const router = useRouter();
const handleDismiss = (count) => {
if (router.canDismiss()) {
router.dismiss(count)
}
};
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Button title="Maybe dismiss" onPress={() => handleDismiss()} />
</View>
);
}
For a list of all options, see React Navigation's documentation.