|
| 1 | +--- |
| 2 | +title: React Navigation 8.0 - March Progress Report |
| 3 | +authors: satya |
| 4 | +tags: [announcement] |
| 5 | +--- |
| 6 | + |
| 7 | +We've been busy since the [first alpha release of React Navigation 8.0](/blog/2025/12/19/react-navigation-8.0-alpha). Our main focuses have been improving the DX around deep linking and TypeScript, and using modern features and patterns such as [`React.Activity`](https://react.dev/reference/react/Activity) and other React features such as [`Suspense`](https://react.dev/reference/react/Suspense), React Native features as [`PlatformColor`](https://reactnative.dev/docs/platformcolor), validation with [Standard Schema](https://standardschema.dev/) etc. |
| 8 | + |
| 9 | +This post covers the features and improvements that landed since the first alpha release back in December 2025. |
| 10 | + |
| 11 | +<!--truncate--> |
| 12 | + |
| 13 | +## Minimum requirements |
| 14 | + |
| 15 | +In order to use the latest React features such as [`Activity`](https://react.dev/reference/react/Activity), [`use`](https://react.dev/reference/react/use) etc., React Navigation 8 requires React 19 or later. This means: |
| 16 | + |
| 17 | +- [React Native 0.83](https://reactnative.dev/blog/2025/12/10/react-native-0.83) or later |
| 18 | +- [Expo SDK 55](https://expo.dev/changelog/sdk-55) or later |
| 19 | + |
| 20 | +## Highlights |
| 21 | + |
| 22 | +### New `inactiveBehavior` option for all navigators |
| 23 | + |
| 24 | +Unlike Web routing libraries, React Navigation keeps unfocused screens mounted to preserve local state and enable smooth transitions - matching native app behavior. The downside is higher memory usage. |
| 25 | + |
| 26 | +Previously, [`react-native-screens`](https://github.com/software-mansion/react-native-screens) helped by detaching inactive screens (`detachInactiveScreens` in supported navigators) from the native view hierarchy. However, the integration was complex and added maintenance burden. So we're working on alternative approaches. |
| 27 | + |
| 28 | +We have added a new `inactiveBehavior` option to all navigators that gives you control over how inactive screens are handled. Currently it supports the following values: |
| 29 | + |
| 30 | +- `pause` - inactive screens are rendered, but effects are cleaned up using [`React.Activity`](https://react.dev/reference/react/Activity) (default) |
| 31 | +- `unmount` - inactive screens are unmounted and remounted when they become active again (only available in [Native Stack](/docs/8.x/native-stack-navigator) and [JS Stack](/docs/8.x/stack-navigator)) |
| 32 | +- `none` - inactive screens are kept mounted as normal |
| 33 | + |
| 34 | +Any subscriptions, timers etc. are cleaned up for paused screens, which would avoid re-rendering paused inactive screens unnecessarily. |
| 35 | + |
| 36 | +Example: |
| 37 | + |
| 38 | +```js |
| 39 | +screenOptions: { |
| 40 | + inactiveBehavior: 'pause', |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +See [navigation lifecycle documentation](/docs/8.x/navigation-lifecycle#inactive-screens) for more details. |
| 45 | + |
| 46 | +If you have specific use cases or feedback on this API, please let us know on [GitHub Discussions](https://github.com/react-navigation/react-navigation/discussions). |
| 47 | + |
| 48 | +### Deep links enabled by default |
| 49 | + |
| 50 | +Previously, deep links were needed to be explicitly enabled by setting `linking.enabled` to `true` or `auto`, or by passing a `linking` prop. This was because users needed to specify at least the `prefixes` array, which was necessary to support environments such as [Expo](https://expo.dev/) which had a special URL prefix. However, this is no longer the case. |
| 51 | + |
| 52 | +So deep linking is now enabled by default when using static configuration without needing to pass any `linking` related options. Paths are automatically generated based on screen names (converting `PascalCase` to `kebab-case`), and custom patterns and parsing logic can be added on a per-screen basis as needed. |
| 53 | + |
| 54 | +If you don't want to enable deep linking, you can still opt out: |
| 55 | + |
| 56 | +```js |
| 57 | +<Navigation |
| 58 | + linking={{ |
| 59 | + enabled: false, |
| 60 | + }} |
| 61 | +/> |
| 62 | +``` |
| 63 | + |
| 64 | +See [configuring links documentation](/docs/8.x/configuring-links) for more details. |
| 65 | + |
| 66 | +### Support for Standard Schema for `linking` |
| 67 | + |
| 68 | +As part of our effort to improve type safety, we have added support for using schemas from a [Standard Schema](https://standardschema.dev/) compatible library such as [Zod](https://zod.dev/), [Valibot](https://valibot.dev/) or [ArkType](https://arktype.io/) in the `parse` property of linking config. |
| 69 | + |
| 70 | +Example with Zod: |
| 71 | + |
| 72 | +```js |
| 73 | +import { z } from 'zod'; |
| 74 | + |
| 75 | +const RootStack = createStackNavigator({ |
| 76 | + screens: { |
| 77 | + Profile: { |
| 78 | + screen: ProfileScreen, |
| 79 | + linking: { |
| 80 | + path: 'user/:id', |
| 81 | + parse: { |
| 82 | + id: z.coerce.number(), |
| 83 | + }, |
| 84 | + }, |
| 85 | + }, |
| 86 | + }, |
| 87 | +}); |
| 88 | +``` |
| 89 | + |
| 90 | +Compared to the parse functions, schemas provide a few advantages: |
| 91 | + |
| 92 | +- **Support for validation and fallback**: A parse function only parses the param. A schema can also validate the param. If the validation fails, the URL won't match the current screen and React Navigation will try the next matching config. Schemas are also called with `undefined` when a query param is missing, which lets them provide a fallback, while parse functions are not called when a query param is missing. |
| 93 | +- **Better Query Param handling with TypeScript**: When using [Static Configuration](/docs/8.x/static-configuration), query params (e.g. `?foo=bar`) are always inferred as optional with `parse` functions. With schemas, you can specify whether a query param is required (e.g. `z.string()`) or optional (e.g. `z.string().optional()`). |
| 94 | + |
| 95 | +See [configuring links guide](/docs/8.x/configuring-links#using-standard-schema) and [typescript guide](/docs/8.x/typescript#parse-function-vs-standard-schema) for more details. |
| 96 | + |
| 97 | +### SF Symbols and Material Symbols |
| 98 | + |
| 99 | +We've added first-class support for [SF Symbols](https://developer.apple.com/sf-symbols/) on iOS and [Material Symbols](https://fonts.google.com/icons) on Android throughout the library. |
| 100 | + |
| 101 | +A new `SFSymbol` component renders a native SF Symbol on iOS: |
| 102 | + |
| 103 | +```js |
| 104 | +import { SFSymbol } from '@react-navigation/native'; |
| 105 | + |
| 106 | +function HeartIcon() { |
| 107 | + return <SFSymbol name="heart.fill" color="tomato" />; |
| 108 | +} |
| 109 | +``` |
| 110 | + |
| 111 | +<video playsInline autoPlay muted loop style={{ width: '400px', aspectRatio: 4 / 5 }}> |
| 112 | + |
| 113 | + <source src="/assets/icons/sf-symbol.mp4" /> |
| 114 | +</video> |
| 115 | + |
| 116 | +And a `MaterialSymbol` component renders a native Material Symbol on Android: |
| 117 | + |
| 118 | +```js |
| 119 | +import { MaterialSymbol } from '@react-navigation/native'; |
| 120 | + |
| 121 | +function HeartIcon() { |
| 122 | + return <MaterialSymbol name="favorite" color="tomato" />; |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +<video playsInline autoPlay muted loop style={{ width: '400px', aspectRatio: 4 / 5 }}> |
| 127 | + |
| 128 | + <source src="/assets/icons/material-symbol.mp4" /> |
| 129 | +</video> |
| 130 | + |
| 131 | +These icons are used across various navigators for tab bar icons, header icons, and more. |
| 132 | + |
| 133 | +See [icons documentation](/docs/8.x/icons) for more details and examples. |
| 134 | + |
| 135 | +### Material themes |
| 136 | + |
| 137 | +In React Navigation 8, we added support for React Native's [`PlatformColor`](https://reactnative.dev/docs/platformcolor) and [`DynamicColorIOS`](https://reactnative.dev/docs/dynamiccolorios) APIs across our components and theming system. |
| 138 | + |
| 139 | +Based on this, we now export 2 new themes based on Material Design 3 on Android: |
| 140 | + |
| 141 | +- `MaterialLightTheme` |
| 142 | +- `MaterialDarkTheme` |
| 143 | + |
| 144 | +These themes support Android 14+ and use dynamic color scheme based on user's preferences or wallpaper. Under the hood, they use `PlatformColor` to reference system colors such as `@android:color/system_primary_light`, `@android:color/system_on_surface_light` etc. |
| 145 | + |
| 146 | +When Material themes are used, some navigators such as `@react-navigation/bottom-tabs` will also adjust their colors to match the Material Design 3 guidelines. |
| 147 | + |
| 148 | +<div className="image-grid" style={{ '--img-width': '360px' }}> |
| 149 | + |
| 150 | + |
| 151 | + |
| 152 | + |
| 153 | + |
| 154 | + |
| 155 | +</div> |
| 156 | + |
| 157 | +See [theming documentation](/docs/8.x/themes) for more details. |
| 158 | + |
| 159 | +### `UNSTABLE_CornerInset` for better iPadOS windowed mode support |
| 160 | + |
| 161 | +We have refactored a lot of our components in React Navigation 8 to minimize the reliance on window dimensions in order to support responsive layouts such as iPadOS windowed mode. Continuing with this effort, we have added a new `UNSTABLE_CornerInset` component. |
| 162 | + |
| 163 | +Previously content such as custom header's back button would get overlapped by the traffic light buttons (close, minimize, maximize) when using the app in iPadOS windowed mode. The `UNSTABLE_CornerInset` component is intended to solve this: |
| 164 | + |
| 165 | +```js |
| 166 | +import { UNSTABLE_CornerInset } from '@react-navigation/native'; |
| 167 | + |
| 168 | +function MyHeader() { |
| 169 | + return ( |
| 170 | + <View style={{ flexDirection: 'row', alignItems: 'center' }}> |
| 171 | + <UNSTABLE_CornerInset direction="horizontal" edge="left" /> |
| 172 | + {/* rest of your content */} |
| 173 | + </View> |
| 174 | + ); |
| 175 | +} |
| 176 | +``` |
| 177 | + |
| 178 | +The component takes the width or height of the traffic light area based on the content direction and the edge of the screen where you want to apply the inset on iPadOS. It renders a plain `View` on other platforms. |
| 179 | + |
| 180 | +<video playsInline autoPlay muted loop style={{ width: '500px', aspectRatio: 5 / 4 }}> |
| 181 | + |
| 182 | + <source src="/assets/blog/8.x/corner-inset.mp4" /> |
| 183 | +</video> |
| 184 | + |
| 185 | +We internally use this in headers, drawer content etc., but you can also use it in your own components if it makes sense for your layout. |
| 186 | + |
| 187 | +### Updated behavior for `getId` in stack navigators |
| 188 | + |
| 189 | +Previously, the ID returned by `getId` was treated as a unique identifier. When using `navigate` or `push` to go to a route with an ID that already existed in the stack, the stack would rearrange to bring the existing route to the front. However, this resulted in broken behavior with Native Stack Navigator and hard to debug issues. |
| 190 | + |
| 191 | +We have changed the behavior of `getId` to treat it more like the route name - it will match routes by ID without rearranging the stack: |
| 192 | + |
| 193 | +- If you're already on a screen with the same ID, it will update its params without pushing a new screen. |
| 194 | +- If you're on a different screen, it will push the new screen onto the stack. |
| 195 | +- If you navigate to a route with an existing ID and `pop: true`, it will pop back to the matching route instead of moving it to the top. |
| 196 | + |
| 197 | +While this is a breaking change, the previous behavior resulted in broken behavior with Native Stack Navigator. So we need to change it to avoid confusion and hard to debug issues. |
| 198 | + |
| 199 | +See [upgrade guide](/docs/8.x/upgrading-from-7.x) for migration details. |
| 200 | + |
| 201 | +### Accessibility improvements on the Web |
| 202 | + |
| 203 | +Previously, unfocused screens on the Web were hidden from assistive technologies using `aria-hidden`, but they could still receive focus and interaction unless hidden with `display: none`, which wasn't possible in all cases. |
| 204 | + |
| 205 | +Now we have a [`inert`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/inert) attribute available on the Web that can be used to make content non-interactive and hidden from assistive technologies without affecting its visibility. We now use it across all navigators on the Web to improve accessibility. |
| 206 | + |
| 207 | +### Navigation events and deep links in Devtools |
| 208 | + |
| 209 | +The `useLogger` hook from `@react-navigation/devtools` previously only showed navigation actions. It now shows the following additional information: |
| 210 | + |
| 211 | +- Deep links received by React Navigation |
| 212 | +- Events emitted by navigators (e.g. `tabPress`) |
| 213 | + |
| 214 | +<img src="/assets/blog/8.x/devtools-logger.png" style={{ width: '425px' }} /> |
| 215 | + |
| 216 | +This should help with debugging navigation issues and understanding when deep links are being handled. |
| 217 | + |
| 218 | +See [Devtools documentation](/docs/8.x/devtools#uselogger) for more details. |
| 219 | + |
| 220 | +### LLM friendly documentation |
| 221 | + |
| 222 | +We have made many improvements to our documentation site to make it more LLM friendly: |
| 223 | + |
| 224 | +- Documentation pages now have a markdown version by appending `.md` to the path (e.g. [https://reactnavigation.org/docs/8.x/getting-started.md](https://reactnavigation.org/docs/8.x/getting-started.md)) |
| 225 | +- Documentation pages now have a "Copy page" button to copy the content of the current page as markdown, or open in ChatGPT and Claude |
| 226 | +- A list of all documentation pages and full documentation content is available at [`llms.txt`](pathname:///llms.txt) and [`llms-full.txt`](pathname:///llms-full.txt) respectively |
| 227 | + |
| 228 | +Hopefully these will make it easier to use React Navigation with LLMs. |
| 229 | + |
| 230 | +## Plans for the future |
| 231 | + |
| 232 | +The [`react-native-screens`](https://github.com/software-mansion/react-native-screens) library is being rewritten with a new implementation for Native Stack. We plan to use it for [`@react-navigation/native-stack`](/docs/8.x/native-stack-navigator) and will try our best to keep the same API as much as possible. |
| 233 | + |
| 234 | +We aim to release a beta version once we have the new `react-native-screens` integration ready, which is a major piece of work that will take some time. Follow us on [X](https://x.com/reactnavigation) to stay updated on the progress. |
| 235 | + |
| 236 | +## Try it out |
| 237 | + |
| 238 | +If you'd like to try the latest changes, install from the `next` tag: |
| 239 | + |
| 240 | +```sh npm2yarn |
| 241 | +npm install @react-navigation/native@next @react-navigation/bottom-tabs@next |
| 242 | +``` |
| 243 | + |
| 244 | +If you encounter any issues or have feedback, please let us know on [GitHub Issues](https://github.com/react-navigation/react-navigation/issues) or [GitHub Discussions](https://github.com/react-navigation/react-navigation/discussions). |
| 245 | + |
| 246 | +## Sponsor us |
| 247 | + |
| 248 | +If React Navigation helps you deliver value to your customers, it'd mean a lot if you could sponsor us. Sponsorships help us move faster toward building the best cross-platform navigation library and continue to provide timely support for bug reports in our GitHub issues. |
| 249 | + |
| 250 | +👉 [Visit our GitHub Sponsors page](https://github.com/sponsors/react-navigation) 👈 |
0 commit comments