HomeGuidesReferenceLearn

Stack

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:

app/_layout.js
import { Stack } from 'expo-router/stack';

export default function Layout() {
  return <Stack />;
}

Configure header bar

Use the screenOptions prop to configure the header bar.

app/_layout.js
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.

app/home.js
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.

app/details.js
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.

app/home.js
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>
  );
}

Header buttons

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.

app/_layout.js
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.

app/home.js
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>
    </>
  );
}

Custom push behavior

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.

app/_layout.js
import { Stack } from 'expo-router';

export default function Layout() {
  return (
    <Stack>
      <Stack.Screen
        name="[user]"
        getId={({ params }) => String(Date.now())}
      />
    </Stack>
  );
}

JavaScript stack with @react-navigation/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:

layouts/js-stack.tsx
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.

app/_layout.js
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.

Removing stack screens

dismiss action

Dismissed 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.

app/settings.js
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 action

To 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.

app/settings.js
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.

app/settings.js
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>
  );
}

More

Native Stack Navigator: Options

For a list of all options, see React Navigation's documentation.