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>
  );
}

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>
  );
}

Next steps

Native Stack Navigator: Options

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

Tabs layout

Learn how to use Tabs layout in Expo Router.

  • Ask a question on the forums

  • Edit this page

Was this doc helpful?