Skip to content

feat:migrate to standard navigation shared adapter - #581

Open
oleksandrzavarzin-callstack wants to merge 15 commits into
callstack:mainfrom
oleksandrzavarzin-callstack:feat/migrate-to-standard-navigation-shared-adapter
Open

oleksandrzavarzin-callstack wants to merge 15 commits into
callstack:mainfrom
oleksandrzavarzin-callstack:feat/migrate-to-standard-navigation-shared-adapter

Conversation

@oleksandrzavarzin-callstack

@oleksandrzavarzin-callstack oleksandrzavarzin-callstack commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

What

Expo SDK 56 dropped Expo Router's dependency on @react-navigation/* in favour of a vendored fork. @bottom-tabs/react-navigation imports the real thing, so on SDK 56+ two copies of React Navigation load and their contexts stop lining up.

This moves the tab view into a new framework-agnostic package, @bottom-tabs/standard-navigation, built on the standard-navigation contract. One implementation, two adapters:

Expo SDK Package Integration
56+ @bottom-tabs/standard-navigation unstable_createStandardRouterNavigator
52 to 55 @bottom-tabs/react-navigation withLayoutContext

@bottom-tabs/react-navigation's public API is unchanged - same exports, same types, same theme-derived tint defaults. It becomes a thin adapter over the shared view.

Notable

  • Dropped the color dependency for React Native's processColor. color is ESM-only, so every consumer who writes tests had to patch transformIgnorePatterns before Jest would run at all.
  • The peer on @react-navigation/native stays at >=7. An earlier revision raised it to >=7.3.0, which broke npm install outright for SDK 52-55 apps locked below that - and nothing on that path needs 7.3.
  • NativeBottomTabsContent carries its event map and navigator props on phantom type-only properties. Without them an integrator loses every navigator prop at the call site: tabBarActiveTintColor and friends become type errors.
  • unstable_createStandardRouterNavigator is marked unstable by Expo and may change between minors. We deliberately don't call it ourselves, so a break stays in app code.
  • On the Expo Router path a custom tabBar receives { state, descriptors, actions, emitter } rather than a navigation object, so @react-navigation/bottom-tabs' BottomTabBar can't be used there. The React Navigation path is unaffected.

How to test

yarn && yarn build
yarn test        # 96 tests across 5 workspaces
yarn lint && yarn typecheck

apps/expo-router-test runs one assertion suite against both adapters under a real Expo Router tree, with a console spy that fails on any warning or error.

Verified on an app scaffolded from create-expo-app --template default@sdk-57 (Expo 57, React Native 0.86.3, React 19.2.3):

  • iOS 26 and Android 17 - builds and bundles, Liquid Glass and Material 3 tab bars render with tint and badge, tapping routes between screens. No JS warnings or errors.
  • @react-navigation/* is absent from node_modules entirely, so there's no second copy to line up - everything resolves through Expo Router's vendored fork.

The SDK 52-55 path was checked separately: the documented withLayoutContext recipe typechecks clean against a real expo-router@55 install, at both 7.1.33 and 7.3.0.

Screenshots

iOS 26 Android 17
image image

@oleksandrzavarzin-callstack
oleksandrzavarzin-callstack marked this pull request as ready for review September 11, 2026 14:20
@@ -0,0 +1,12 @@
---
'@bottom-tabs/navigation': minor

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call this new package @bottom-tabs/standard-navigation to keep consistency with React Navigation

Comment on lines +8 to +12
`NativeBottomTabsContent` carries its event map and navigator props on phantom properties, so an integrator can type `tabBarActiveTintColor`, `tabBar` and the rest. Neither is inferable from the contract on its own - the event map appears only as an argument to `emitter.emit`, and the navigator props only inside an `Omit<…>`.

`createNativeBottomTabNavigator` renders through the shared navigator, and keeps building its own state with `useNavigationBuilder`, so `state.key` and the per-route `navigation` and `route` objects a custom `tabBar` reads stay intact.

`@bottom-tabs/react-navigation` now requires `@react-navigation/native` 7.3.0 or newer, which is the version that introduced `createStandardNavigationFactories` used by the documented shared-navigator recipe

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this is unnecessary for the changeset

Match the naming of @bottom-tabs/react-navigation, and trim the
changeset down to the release-note facts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
<PackageManagerTabs command="install react-native-bottom-tabs" />

If you are going to use [React Navigation / Expo Router Integration](/docs/guides/usage-with-react-navigation) make sure to install `@bottom-tabs/react-navigation`.
If you are going to use the [React Navigation integration](/docs/guides/usage-with-react-navigation), also install `@bottom-tabs/react-navigation`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be here React Navigation or Expo SDK < 56? Or I'm missing something?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's true.
I've added "Expo SDK 55+" note to it.

@@ -0,0 +1,116 @@
import { NavigationContainer } from '@react-navigation/native';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this file specifically, but the example app should be named example-expo-router to follow the convention of the other example apps

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a test harness rather than the example Expo router app (for that option, we will have expo-template, after it's updated). It just checks whether our two adapters, driven by a real Expo Router and a real React Navigation, pass the same correct props to the tab view.

Comment thread docs/docs/docs/guides/usage-with-expo-router.mdx

### Custom tab bar

The `tabBar` function receives `state`, `descriptors`, `actions` and `emitter` - the vocabulary of the navigation contract rather than a React Navigation `navigation` object. If your tab bar needs the router, call `useNavigation()` from `expo-router` inside it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove AI text

Suggested change
The `tabBar` function receives `state`, `descriptors`, `actions` and `emitter` - the vocabulary of the navigation contract rather than a React Navigation `navigation` object. If your tab bar needs the router, call `useNavigation()` from `expo-router` inside it.
The `tabBar` function receives `state`, `descriptors`, `actions` and `emitter`. If your tab bar needs the router, call `useNavigation()` from `expo-router` inside it.


The `tabBar` function receives `state`, `descriptors`, `actions` and `emitter` - the vocabulary of the navigation contract rather than a React Navigation `navigation` object. If your tab bar needs the router, call `useNavigation()` from `expo-router` inside it.

`BottomTabBar` from `@react-navigation/bottom-tabs` cannot be used here, since it expects a React Navigation `navigation` object.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants