Skip to main content
Version: 7.x

Upgrading from 6.x

React Navigation 7 focuses on streamlining the API to avoid patterns that can cause bugs. This means deprecating some of the legacy behavior kept for backward compatibility reasons.

This guides lists all the breaking changes and new features in React Navigation 7 that you need to be aware of when upgrading from React Navigation 6.

Minimum Requirements

  • react-native >= 0.72.0
  • expo >= 49 (if you use Expo)
  • typescript >= 5.0.0 (if you use TypeScript)

Breaking changes

Changes to the navigate action

The navigate method no longer navigates to screen in a nested child navigator

Due to backward compatibility reasons, React Navigation 5 and 6 support navigating to a screen in a nested child navigator with navigation.navigate(ScreenName) syntax. But this is problematic:

  • It only works if the navigator is already mounted - making navigation coupled to other logic.
  • It doesn't work with the TypeScript types.

Due to these issues, we have a special API to navigate to a nested screen (navigation.navigate(ParentScreenName, { screen: ScreenName })).

From these release, this is no longer the default behavior. If you're relying on this behavior in your app, you can pass the navigationInChildEnabled prop to NavigationContainer to keep the behavior until you are able to migrate:

<NavigationContainer navigationInChildEnabled>{/* ... */}</NavigationContainer>

The navigationInChildEnabled prop will be removed in the next major.

See navigate for updated usage.

The navigate method no longer goes back in, use popTo instead

Previously, navigate method navigated back if the screen already exists in the stack. We have seen many people get confused by this behavior.

To avoid this confusion, we have removed the going back behavior from navigate and added a new method popTo to explicitly go back to a specific screen in the stack:

navigation.navigate('PreviousScreen', { foo: 42 });
navigation.popTo('PreviousScreen', { foo: 42 });

The methods now behave as follows:

  • navigate(screenName) will stay on the current screen if the screen is already focused, otherwise push a new screen to the stack.
  • popTo(screenName) will go back to the screen if it exists in the stack, otherwise pop the current screen and add this screen to the stack.

See popTo for more details.

To achieve a behavior similar to before with navigate, you can use the getId prop in which case it'll go to the screen with the matching ID and push or pop screens accordingly.

To help with the migration, we have added a new method called navigateDeprecated which will behave like the old navigate method. You can replace your current navigate calls with navigateDeprecated to gradually migrate to the new behavior:


The navigateDeprecated method will be removed in the next major.

The navigate method no longer accepts a key option

Previously, you could specify a route key to navigate to, e.g.:

navigation.navigate({ key: 'someuniquekey' })`

It's problematic since:

  • key is an internal implementation detail and created by the library internally - which makes it weird to use.
  • None of the other actions support such usage.
  • Specifying a key is not type-safe, making it easy to cause bugs.

In React Navigation 5, we added the getId prop which can be used for similar use cases - and gives users full control since they provide the ID and it's not autogenerated by the library.

So the key option is now being removed from the navigate action.

See navigate for updated usage.

Changes to NavigationContainer

The onReady callback on NavigationContainer now fires only when there are navigators rendered

Previously, the onReady prop and navigationRef.isReady() worked slightly differently:

  • The onReady callback fired when NavigationContainer finishes mounting and deep links is resolved.
  • The navigationRef.isReady() method additionally checks if there are any navigators rendered - which may not be true if the user is rendering their navigators conditionally inside a NavigationContainer.

This is important to know since if no navigator is rendered, we can't dispatch any navigation actions as there's no navigator to handle them. But the inconsistency between onReady and navigationRef.isReady() made it easy to cause issues and confusion.

This changes onReady to work similar to navigationRef.isReady(). The onReady callback will now fire only when there are navigators rendered - reflecting the value of navigationRef.isReady().

This change is not breaking for most users, so you may not need to do anything.

See onReady for usage.

The independent prop on NavigationContainer is removed in favor of NavigationIndependentTree component

The independent prop on NavigationContainer was added to support rendering navigators in a separate tree from the rest of the app. This is useful for use cases such as miniapps.

However, there are issues with this approach:

  • When building a miniapp, the responsibility of adding this prop was on the miniapp developer, which isn't ideal since forgetting it can cause problems.
  • A lot of beginners mistakenly added this prop and were confused why navigation wasn't working.

So we've removed this prop instead of a NavigationIndependentTree component which you can use to wrap the navigation container:

<NavigationContainer independent>
{/* ... */}
{/* ... */}

This way, the responsibility no longer lies on the miniapp developer, but on the parent app. It's also harder for beginners to accidentally add this.

See Independent navigation containers for usage.

The theme prop now accepts a fonts property

Previously, the theme prop on NavigationContainer accepted a colors property to customize the colors used by various UI elements from React Navigation. We have now added a fonts property to customize the fonts as well. If you are passing a custom theme in the theme prop, you'll need to update it to include the fonts property.

import { DefaultTheme } from '@react-navigation/native';

const theme = {
colors: {
// ...
fonts: DefaultTheme.fonts,

If you want to customize the fonts, see the themes guide for more details.

Changes to linking

Encoding of params in path position is now more relaxed

Previously, params were always URL encoded with encodeURIComponent regardless of their position (e.g. query position such as ?user=jane or path position such as /users/jane) when generating a link for a screen (e.g. URL on the Web). This made it hard to use special characters in the params.

Now, only the params in the query position are URL encoded. For the params in the path position, we only encode the characters that are not allowed in the path position.

With this change, it's easier to use special characters such as @ in the path. For example, to have a URL such as profile/@username, you can use the following in the linking config:

const config = {
prefixes: [''],
config: {
screens: {
Profile: {
path: 'profile/:username',
parse: {
username: (username) => username.replace(/^@/, ''),
stringify: {
username: (username) => `@${username}`,

See Configuring links for usage of the linking config.

Previously, the Link component and useLinkProps hook were designed to work with path strings via the to prop. But it had few issues:

  • The path strings are not type-safe, making it easy to cause typos and bugs after refactor
  • The API made navigating via screen name more inconvenient, even if that's the preferred approach

Now, instead of the to prop that took a path string, they now accept screen and params props, as well as an optional href prop to use instead of the generated path:

<Link to="/details?foo=42">Go to Details</Link>
<Link screen="Details" params={{ foo: 42 }}>Go to Details</Link>


const props = useLinkProps({ to: '/details?foo=42' });
const props = useLinkProps({ screen: 'Details', params: { foo: 42 } });

With this change, you'd now have full type-safety when using the Link component given that you have configured the global type.

See Link and useLinkProps for usage.

The useLinkBuilder hooks now returns an object instead of a function

Previously, the useLinkBuilder hooks returned a function to build a href for a screen - which is primarily useful for building custom navigators. Now, it returns an object with buildHref and buildAction methods:

const { buildHref, buildAction } = useLinkBuilder();

const href = buildHref('Details', { foo: 42 }); // '/details?foo=42'
const action = buildAction('/details?foo=42'); // { type: 'NAVIGATE', payload: { name: 'Details', params: { foo: 42 } } }

The buildHref method acts the same as the previously returned function. The new buildAction method can be used to build a navigation action from a href string.

Note that this hook is intended to be primarily used by custom navigators and not by end users. For end users, the Link component and useLinkProps are the recommended way to navigate.

See useLinkBuilder for usage.

Changes to navigators

Screens pushed on top of modals are now shown as modals in the Stack and Native Stack navigators

Previously, screens pushed on top of modals were shown as regular screens in the Stack and Native Stack navigators. This often caused glitchy animation on Stack Navigator and appeared behind the modal on Native Stack Navigator. This can be especially confusing if the user came to the screen from a deep link.

Now, screens pushed on top of modals are automatically shown as modals to avoid these issues. This behavior can be disabled by explicitly setting the presentation option to card:

presentation: 'card',

See Stack Navigator and Native Stack Navigator docs for usage.

animationEnabled option is removed in favor of animation option in Stack Navigator

Previously, animationEnabled: false was used to disable the animation for the screen transition in Stack Navigator.

There's now a new animation prop to configure animations similar to the Native Stack. So you can now use animation: 'none' to disable the animation instead:

animationEnabled: false,
animation: 'none',

See Stack Navigator animation for usage.

customAnimationOnGesture is renamed to animationMatchesGesture in Native Stack Navigator

The customAnimationOnGesture option in Native Stack Navigator is renamed to animationMatchesGesture to better reflect its purpose. If you are using customAnimationOnGesture in your project, you can rename it to animationMatchesGesture:

<Stack.Navigator options={{ customAnimationOnGesture: true }}>
<Stack.Navigator options={{ animationMatchesGesture: true }}>

See Native Stack Navigator for usage.

Material Top Tab Navigator no longer requires installing react-native-tab-view

Previously, @react-navigation/material-top-tabs required installing react-native-tab-view as a dependency in the project. We have now moved this package to the React Navigation monorepo and able to coordinate the releases together, so it's no longer necessary to install it separately.

If you use @react-navigation/material-top-tabs and don't use react-native-tab-view anywhere else in your project, you can remove it from your dependencies after upgrading.

If you need to enforce a specific version of react-native-tab-view for some reason, we recommend using Yarn resolutions or npm overrides to do so.

See Material Top Tab Navigator for usage.

The unmountOnBlur option is removed in favor of popToTopOnBlur in Bottom Tab Navigator


The tabBarTestID option is renamed to tabBarButtonTestID in Bottom Tab Navigator and Material Top Tab Navigator

The tabBarTestID option in @react-navigation/bottom-tabs and @react-navigation/material-top-tabs is renamed to tabBarButtonTestID to better reflect its purpose. If you are using tabBarTestID in your project, you can rename it to tabBarButtonTestID:

<Tab.Navigator tabBarOptions={{ tabBarTestID: 'test-id' }}>
<Tab.Navigator tabBarOptions={{ tabBarButtonTestID: 'test-id' }}>

See Bottom Tab Navigator and Material Top Tab Navigator docs for usage.

Drawer Navigator now requires Reanimated 2 or 3 on native platforms

Previously, @react-navigation/drawer supported both Reanimated 1 and Reanimated 2 APIs with the useLegacyImplementation option. This is now no longer supported and the useLegacyImplementation option is removed.

If you are using Reanimated 1 in your project, you'll need to upgrade to Reanimated 2 or 3 to use @react-navigation/drawer.

If you're using Drawer Navigator on the Web, it'll now use CSS transitions instead of Reanimated for a smaller bundle size.

See Drawer Navigator for usage.

Deprecations and removals

Material Bottom Tab Navigator now lives in react-native-paper package

The @react-navigation/material-bottom-tabs package provided React Navigation integration for react-native-paper's BottomNavigation component. To make it easier to keep it updated with the changes in react-native-paper, we have now moved it to the react-native-paper package.

If you are using @react-navigation/material-bottom-tabs in your project, you can remove it from your dependencies and change the imports to react-native-paper/react-navigation instead:

import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
import { createMaterialBottomTabNavigator } from 'react-native-paper/react-navigation';

See Material Bottom Tab Navigator for usage.

The flipper devtools plugin is now removed

Previously, we added a Flipper plugin for React Navigation to make debugging navigation easier. However, it has added significant maintenance overhead for us. The Flipper team hasn't been focused on React Native recently, so the overall experience of using Flipper with React Native has been poor.

Currently, the Flipper team has been focused on native developer experience, so we are going back to the drawing board. We have created a new pillar within our team focused on Developer Experience. We are currently investigating improved Chrome Debugger protocol support from the Hermes team as well as migrating the debugging experience from Flipper to Chrome DevTools so we can deliver a debugging experience that meets our standard.

react-native-community/discussions-and-proposals#546 (comment)

Since the React Native team migrating away from Flipper, it doesn't make much sense for us to spend additional resources to keep supporting it. So we've removed the Flipper plugin from @react-navigation/devtools.

Various deprecated APIs are removed

We have removed all of the previously deprecated APIs. These APIs were deprecated in React Navigation 6 and showed a warning when used. So make sure that you have addressed all the warnings before upgrading.

Full list of removed APIs
  • @react-navigation/stack
    • Removed mode prop - use presentation option instead
    • Removed headerMode prop - use headerMode and headerShown options instead
    • Removed keyboardHandlingEnabled prop - use keyboardHandlingEnabled option instead
  • @react-navigation/drawer
    • Removed openByDefault prop - use defaultStatus prop instead
    • Removed lazy prop - use lazy option instead
    • Removed drawerContentOptions prop which contained following options:
      • drawerPosition - use drawerPosition option instead
      • drawerType - use drawerType option instead
      • edgeWidth - use swipeEdgeWidth option instead
      • hideStatusBar - use drawerHideStatusBarOnOpen option instead
      • keyboardDismissMode - use keyboardDismissMode option instead
      • minSwipeDistance - use swipeMinDistance option instead
      • overlayColor - use overlayColor option instead
      • statusBarAnimation - use drawerStatusBarAnimation option instead
      • gestureHandlerProps - use gestureHandlerProps option instead
  • @react-navigation/bottom-tabs
    • Removed lazy prop - use lazy option instead
    • Removed tabBarOptions prop which contained following options:
      • keyboardHidesTabBar - use tabBarHideOnKeyboard option instead
      • activeTintColor - use tabBarActiveTintColor option instead
      • inactiveTintColor - use tabBarInactiveTintColor option instead
      • activeBackgroundColor - use tabBarActiveBackgroundColor option instead
      • inactiveBackgroundColor - use tabBarInactiveBackgroundColor option instead
      • allowFontScaling - use tabBarAllowFontScaling option instead
      • showLabel - use tabBarShowLabel option instead
      • labelStyle - use tabBarLabelStyle option instead
      • iconStyle - use tabBarIconStyle option instead
      • tabStyle - use tabBarItemStyle option instead
      • labelPosition and adapative - use tabBarLabelPosition option instead
      • tabBarVisible - use display: 'none' tabBarStyle option instead
  • @react-navigation/material-top-tabs
    • Removed swipeEnabled prop - use swipeEnabled option instead
    • Removed lazy prop - use lazy option instead
    • Removed lazyPlaceholder prop - use lazyPlaceholder option instead
    • Removed lazyPreloadDistance prop - use lazyPreloadDistance option instead
    • Removed tabBarOptions prop which contained following options: - renderBadge - use tabBarBadge option instead - renderIndicator - use tabBarIndicator option instead - activeTintColor - use tabBarActiveTintColor option instead - inactiveTintColor - use tabBarInactiveTintColor option instead - pressColor - use tabBarPressColor option instead - pressOpacity - use tabBarPressOpacity option instead - showLabel - use tabBarShowLabel option instead - showIcon - use tabBarShowIcon option instead - allowFontScaling - use tabBarAllowFontScaling option instead - bounces - use tabBarBounces option instead - scrollEnabled - use tabBarScrollEnabled option instead - iconStyle - use tabBarIconStyle option instead - labelStyle - use tabBarLabelStyle option instead - tabStyle - use tabBarItemStyle option instead - indicatorStyle - use tabBarIndicatorStyle option instead - indicatorContainerStyle - use tabBarIndicatorContainerStyle option instead - contentContainerStyle - use tabBarContentContainerStyle option instead - style - use tabBarStyle option instead


Various UI elements now follow Material Design 3 guidelines

Previously, the UI elements in React Navigation such as the header on platforms other than iOS, drawer, material top tabs etc. were following the Material Design 2 guidelines. We have now updated them to follow the Material Design 3 guidelines.

React Native Tab View now has a new API to specify various options

The API for the TabBar component in react-native-tab-view has been revamped. Previously, it took the following props:

  • getLabelText
  • getAccessible
  • getAccessibilityLabel
  • getTestID
  • renderIcon
  • renderLabel
  • renderBadge

These props have been replaced with commonOptions and options props:

icon: ({ route, focused, color }) => (
<Icon name={route.icon} color={color} />
albums: {
labelText: 'Albums',
profile: {
labelText: 'Profile',

The new API will make it easier for us to improve re-rendering performance of the tab bar items in the library.

See React Native Tab View for usage.

Custom navigators now require more type information

Custom navigators now require more type information to work correctly so that we can provide better type-checking and autocompletion in TypeScript when using the navigator.

export const createMyNavigator =  createNavigatorFactory<
typeof MyNavigator
export function createMyNavigator<
ParamList extends ParamListBase,
NavigatorID extends string | undefined = undefined,
TypeBag extends NavigatorTypeBagBase = {
ParamList: ParamList;
NavigatorID: NavigatorID;
State: MyNavigationState<ParamList>;
ScreenOptions: MyNavigationOptions;
EventMap: MyNavigationEventMap;
NavigationList: {
[RouteName in keyof ParamList]: MyNavigationProp<
Navigator: typeof MyNavigator;
Config extends StaticConfig<TypeBag> | undefined =
| StaticConfig<TypeBag>
| undefined,
>(config?: Config): TypedNavigator<TypeBag, Config> {
return createNavigatorFactory(MyNavigator)(config);

See Custom navigators for usage.

New features

Static configuration API

React Navigation 5 introduced a dynamic API to support more flexible use cases. With React Navigation 7, we are re-introducing a static configuration API:

import { createNativeStackNavigator } from 'react-native-screens/native-stack';

const MyStack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeScreen,
options: {
title: 'My App',
Details: {
screen: DetailsScreen,
linking: 'details/:id',

The static configuration API provides the following benefits:

  • Simpler type-checking with TypeScript: It's not necessary to specify screens and their params separately. See Type checking with TypeScript for more details.
  • Easier deep linking setup: Paths can be generated automatically. Linking configuration can be defined next to the screen for explicit configuration. See Configuring links for more details.

It's also possible to mix the static and dynamic configuration APIs. For example, you can use the static configuration API for the top-level navigators and the dynamic configuration API for the nested navigators where you need more flexibility.


The static configuration API doesn't replace the dynamic configuration API. Both APIs are equally supported and you can choose the one that fits your use case better.

You can see examples for both the static and dynamic configuration APIs in the documentation by selecting the appropriate tab in the examples.

Go to "Hello React Navigation" to start writing some code with the static API.

Improved TypeScript support

Previously, the navigation object received in options and listeners callbacks were typed as any and required manual type annotation. Now, the navigation object has a more accurate type based on the navigator it's used in, and the type annotation is no longer required.

We also export a new XOptionsArgs (where X is the navigator name, e.g. StackOptionsArgs, BottomTabOptionsArgs etc.) type which can be used to type the arguments of the options callback. This can be useful if you want to define the options callback separately.

const options = ({
}: StackOptionsArgs<RootStackParamList, 'Details'>) => {
return {
title: route.params.title,

The options callback gets theme

The options callback now receives the theme object to allow customizing the UI elements specified in the options:

options={({ theme }) => ({
headerRight: () => (
onPress={() => {}}

See Screen options for usage.

Top-level path in linking config

The linking configuration now supports a top-level path configuration to define the base path for all the screens in the navigator:

const linking = {
prefixes: [''],
config: {
path: 'app',
screens: {
Home: 'home',
Details: 'details/:id',

This can be useful if your app lives under a subpath on the web. For example, if your app lives under, you can define the path as app and the Details screen will be accessible at

See Configuring links for usage.

Improved Web integration

More built-in UI elements that trigger navigation now render a tags on the Web for better accessibility and SEO. This includes:

  • Back button in the header
  • The tab buttons in material top tab navigator

UI elements such as the bottom tab bar and drawer items already rendered a tags on the Web.

New usePreventRemove hook

Previously, the only way to prevent a screen from being removed from the stack was to use the beforeRemove event. This didn't work well with the Native Stack Navigator.

The new usePreventRemove hook is an alternative to beforeRemove that works with the Native Stack Navigator.

See usePreventRemove for usage.

New layout props

For navigators

Navigators now support a layout prop. It can be useful for augmenting the navigators with additional UI with a wrapper. The difference from adding a regular wrapper is that the code in layout callback has access to the navigator's state, options etc.:

layout={({ children, state, descriptors, navigation }) => (
<View style={styles.container}>
<Breadcrumbs />
{/* ... */}

See Navigator layout for usage.

For screens and groups

The layout prop makes it easier to provide things such as a global error boundary and suspense fallback for a group of screens without having to manually add HOCs for every screen separately.

It can be used for a single screen with layout:

layout={({ children }) => (
<View style={styles.fallback}>
<Text style={styles.text}>Loading…</Text>

Or with a group or navigator with screenLayout:

screenLayout={({ children }) => (
<View style={styles.fallback}>
<Text style={styles.text}>Loading…</Text>
{/* screens */}

Preloading screens

Many navigators now support preloading screens prior to navigating to them. This can be useful to improve the perceived performance of the app by preloading the screens that the user is likely to navigate to next. Preloading a screen will render it off-screen and execute its side-effects such as data fetching.

To preload a screen, you can use the preload method on the navigation object:

navigation.preload('Details', { id: 42 });

Preloading is supported in the following navigators:

  • Stack Navigator
  • Bottom Tab Navigator
  • Material Top Tab Navigator
  • Drawer Navigator

The Native Stack navigator doesn't currently support preloading screens.

See preload for usage.

Improvements to navigators

Bottom Tab Navigator can now show tabs on the side and top

The @react-navigation/bottom-tabs package now supports showing tabs on the side. This will make it easier to build responsive UIs for where you want to show tabs on the bottom on smaller screens and switch to a sidebar on larger screens.

Similarly, showing tabs on the top is also supported which can be useful for Android TV or Apple TV apps.

You can use the tabBarPosition option to customize the position of the tabs:

tabBarPosition: 'left',
{/* ... */}

See Bottom Tab Navigator options for usage.

Bottom Tab Navigator now supports animations

The @react-navigation/bottom-tabs package now supports animations. This was one of the most requested features on our Canny board: TabNavigator Custom Transition.

You can use the animation option to customize the animations for the tab transitions:

animation: 'fade',
{/* ... */}

See Bottom Tab Navigator animation for usage.

Stack Navigator now supports an animation option

The @react-navigation/stack package now supports an animation option to customize the animations for the screen transitions:

animation: 'slide_from_right',
{/* ... */}

The animation option is an alternative to the TransitionPresets API, and is intended to make migrating between JS stack and native stack navigators easier.

See Stack Navigator animation for usage.

Native Stack Navigator now exports a useAnimatedHeaderHeight hook

The @react-navigation/native-stack package now exports a useAnimatedHeaderHeight hook. It can be used to animate content based on the header height changes - such as when the large title shrinks to a small title on iOS:

const headerHeight = useAnimatedHeaderHeight();

return (
<Animated.View style={{ transform: { translateY: headerHeight } }}>
{/* ... */}

New components in elements library

The @react-navigation/elements package now includes new components that can be used in your app:


The Button component has built-in support for navigating to screens, and renders an anchor tag on the Web when used for navigation:

<Button screen="Profile" params={{ userId: 'jane' }}>
View Jane's Profile

It can also be used as a regular button:

onPress={() => {
/* do something */
Do something

The button follows the Material Design 3 guidelines.

See Button for usage.


The HeaderButton component can be used to render buttons in the header with appropriate styling:

headerRight: ({ tintColor }) => (
accessibilityLabel="More options"
onPress={() => {
/* do something */

See HeaderButton for usage.


The Label component can be used to render label text, such as the label in a tab bar button:


See Label for usage.

react-native-drawer-layout package

The drawer implementation used in @react-navigation/drawer is now available as a standalone package called react-native-drawer-layout. This makes it easier to use the drawer implementation even if you're not using React Navigation, or if you want to use it without a navigator.

You can install it with:

npm install react-native-drawer-layout

See react-native-drawer-layout for usage.