Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ee9006d
test(react-navigation): cover the current navigator behaviour
oleksandrzavarzin-callstack Sep 10, 2026
eff21b1
feat(navigation): add @bottom-tabs/navigation
oleksandrzavarzin-callstack Sep 10, 2026
14e3e17
feat(react-navigation): delegate to @bottom-tabs/navigation
oleksandrzavarzin-callstack Sep 10, 2026
978f887
test(expo-router-test): add an expo-router integration harness
oleksandrzavarzin-callstack Sep 10, 2026
2b2268c
docs: document the standard-navigation entry for Expo Router
oleksandrzavarzin-callstack Sep 11, 2026
56c2c7f
refactor(react-navigation): drop the color dependency
oleksandrzavarzin-callstack Sep 11, 2026
3dcd9e3
fix(react-navigation): keep the full tab bar contract
oleksandrzavarzin-callstack Sep 11, 2026
67f3332
docs: correct the Expo Router warning and list the new package
oleksandrzavarzin-callstack Sep 11, 2026
dbc0727
fix(react-navigation): record the peer bump in the lockfile
oleksandrzavarzin-callstack Sep 11, 2026
b46c8bf
fix(navigation): let integrators infer the navigator props
oleksandrzavarzin-callstack Sep 11, 2026
33363a5
docs: type the shared navigator recipe
oleksandrzavarzin-callstack Sep 11, 2026
ec37666
refactor: rename the shared package to @bottom-tabs/standard-navigation
oleksandrzavarzin-callstack Sep 15, 2026
5d71839
fix: skip packages without a manifest when building the babel aliases
oleksandrzavarzin-callstack Sep 15, 2026
2cc28f1
docs: add quick-start for Expo≤55
oleksandrzavarzin-callstack Sep 16, 2026
a65309c
fix(react-navigation): restore the >=7 peer on @react-navigation/native
oleksandrzavarzin-callstack Sep 16, 2026
cd2b30d
docs: remove unnecessary text
oleksandrzavarzin-callstack Sep 22, 2026
bc455e1
fix(react-navigation): restore the color dependency
oleksandrzavarzin-callstack Oct 15, 2026
8d5258d
ci: run the unit tests
oleksandrzavarzin-callstack Oct 15, 2026
4a9c81e
test: replace the expo-router-test harness with type checks
oleksandrzavarzin-callstack Oct 15, 2026
d02cd3b
Merge branch 'main' into feat/migrate-to-standard-navigation-shared-a…
oleksandrzavarzin-callstack Oct 15, 2026
5fdb7a9
Merge branch 'main' into feat/migrate-to-standard-navigation-shared-a…
oleksandrzavarzin-callstack Oct 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"fixed": [
[
"react-native-bottom-tabs",
"@bottom-tabs/react-navigation"
"@bottom-tabs/react-navigation",
"@bottom-tabs/standard-navigation"
]
],
"linked": [],
Expand Down
6 changes: 6 additions & 0 deletions .changeset/standard-navigation-shared-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@bottom-tabs/standard-navigation': minor
'@bottom-tabs/react-navigation': minor
---

Move the native tabs implementation into a new framework-agnostic `@bottom-tabs/standard-navigation` package built on the `standard-navigation` contract, so Expo Router SDK 56+ apps can drive the same navigator through `unstable_createStandardRouterNavigator` without loading a second copy of React Navigation.
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ jobs:
- name: Typecheck files
run: yarn typecheck

test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

- name: Setup
uses: ./.github/actions/setup

- name: Run unit tests
run: yarn test

swift-lint:
runs-on: ubuntu-latest
steps:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ https://github.com/user-attachments/assets/09e96ac3-827d-4ac0-add0-e7b88ee9197c
| [react-native-bottom-tabs](/packages/react-native-bottom-tabs) | [![badge](https://img.shields.io/npm/v/react-native-bottom-tabs?style=for-the-badge)](https://www.npmjs.com/package/react-native-bottom-tabs) |
| [@bottom-tabs/expo-template](/packages/expo-template) | [![badge](https://img.shields.io/npm/v/@bottom-tabs/expo-template.svg?style=for-the-badge)](https://www.npmjs.com/package/@bottom-tabs/expo-template) |
| [@bottom-tabs/react-navigation](/packages/react-navigation) | [![badge](https://img.shields.io/npm/v/@bottom-tabs/react-navigation.svg?style=for-the-badge)](https://www.npmjs.com/package/@bottom-tabs/react-navigation) |
| [@bottom-tabs/standard-navigation](/packages/standard-navigation) | [![badge](https://img.shields.io/npm/v/@bottom-tabs/standard-navigation.svg?style=for-the-badge)](https://www.npmjs.com/package/@bottom-tabs/standard-navigation) |

## Documentation

Expand Down
9 changes: 4 additions & 5 deletions docs/docs/docs/getting-started/quick-start.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { PackageManagerTabs } from '@theme';

If you don't have an existing project, you can create a new [Expo](https://expo.dev) app using the following command:


```sh
npx create-expo-app@latest NativeTabs --template @bottom-tabs/expo-template
```
Expand All @@ -15,16 +14,18 @@ npx create-expo-app@latest NativeTabs --template @bottom-tabs/expo-template

<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), or [Expo Router](/docs/guides/usage-with-expo-router) on SDK 52 to 55, also install `@bottom-tabs/react-navigation`.

<PackageManagerTabs command="install @bottom-tabs/react-navigation" />

For [Expo Router](/docs/guides/usage-with-expo-router) on SDK 56 or newer, install `@bottom-tabs/standard-navigation` instead.

<PackageManagerTabs command="install @bottom-tabs/standard-navigation" />

### Expo

Add the library plugin in your `app.json` config file and [create a new build](https://docs.expo.dev/develop/development-builds/create-a-build/).


```diff
"expo": {
+ "plugins": ["react-native-bottom-tabs"]
Expand All @@ -42,7 +43,6 @@ This library is not supported in [Expo Go](https://expo.dev/go).

Edit `android/app/src/main/res/values/styles.xml` to inherit from provided theme in order to customize the appearance of the native bottom tabs.


```diff
<resources>
- <style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
Expand All @@ -54,7 +54,6 @@ Edit `android/app/src/main/res/values/styles.xml` to inherit from provided theme

Here you can read more about [Android Native Styling](/docs/guides/android-native-styling).


### iOS with Swift Package Manager

The library ships a `Package.swift` for React Native's SwiftPM autolinking flow.
Expand Down
120 changes: 87 additions & 33 deletions docs/docs/docs/guides/usage-with-expo-router.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,65 @@ import { PackageManagerTabs } from '@theme';

# Usage with Expo Router

First install `@bottom-tabs/react-navigation` which provides a native bottom tab navigator for React Navigation.
Which package you need depends on your Expo SDK version, because Expo Router stopped depending on React Navigation starting from SDK 56.

<PackageManagerTabs command="install @bottom-tabs/react-navigation" />
| Expo SDK | Package | Integration |
| ------------ | ---------------------------------- | ---------------------------------------- |
| 56 and newer | `@bottom-tabs/standard-navigation` | `unstable_createStandardRouterNavigator` |
| 52 to 55 | `@bottom-tabs/react-navigation` | `withLayoutContext` |

Next, create a custom layout adapter for the native bottom tabs using `withLayoutContext` from Expo Router. Put it in `components/bottom-tabs.tsx` and export the navigator, so your layouts can import it:
Expo SDK 51 and older is not supported. Those versions ship React Navigation 6, and `@bottom-tabs/react-navigation` requires React Navigation 7.

```tsx
import { withLayoutContext } from 'expo-router';
import {
createNativeBottomTabNavigator,
NativeBottomTabNavigationOptions,
NativeBottomTabNavigationEventMap,
} from '@bottom-tabs/react-navigation';
import { ParamListBase, TabNavigationState } from '@react-navigation/native';
## Expo SDK 56 and newer
Comment thread
thiagobrez marked this conversation as resolved.

const BottomTabNavigator = createNativeBottomTabNavigator().Navigator;
Install `@bottom-tabs/standard-navigation`, which provides a native bottom tab navigator built on the [`standard-navigation`](https://github.com/react-navigation/standard-navigation) contract that Expo Router speaks.

export const Tabs = withLayoutContext<
NativeBottomTabNavigationOptions,
typeof BottomTabNavigator,
TabNavigationState<ParamListBase>,
NativeBottomTabNavigationEventMap
>(BottomTabNavigator);
```
<PackageManagerTabs command="install @bottom-tabs/standard-navigation" />

> [!NOTE] Make sure to provide the correct types for the `withLayoutContext` function. Without it screen options won't have proper types.
:::warning
Do not install `@bottom-tabs/react-navigation` in an Expo Router 56+ app. Expo Router bundles its own copy of React Navigation.
:::

Create the navigator by handing the tab view to Expo Router's `unstable_createStandardRouterNavigator`. Put it in `components/bottom-tabs.tsx` and export it, so your layouts can import it:

```tsx
import { NativeBottomTabsContent } from '@bottom-tabs/standard-navigation';
import { TabRouter, unstable_createStandardRouterNavigator } from 'expo-router';

export const Tabs = unstable_createStandardRouterNavigator(
NativeBottomTabsContent,
TabRouter
);
```

Then, use the `Tabs` navigator in your app:
Then use it in your layout:

```tsx
import { Platform } from "react-native";
import { Platform } from 'react-native';

import { Tabs } from "@/components/bottom-tabs";
import { Tabs } from '@/components/bottom-tabs';

export default function TabLayout() {
return (
<Tabs>
<Tabs.Screen
name="index"
options={{
title: "Home",
title: 'Home',
tabBarIcon: () =>
Platform.OS === "ios"
? { sfSymbol: "house" }
: require("@/assets/icons/house.png"),
Platform.OS === 'ios'
? { sfSymbol: 'house' }
: require('@/assets/icons/house.png'),
}}
/>
<Tabs.Screen
name="explore"
options={{
title: "Explore",
title: 'Explore',
tabBarIcon: () =>
Platform.OS === "ios"
? { sfSymbol: "person" }
: require("@/assets/icons/person.png"),
Platform.OS === 'ios'
? { sfSymbol: 'person' }
: require('@/assets/icons/person.png'),
}}
/>
</Tabs>
Expand All @@ -66,7 +70,57 @@ export default function TabLayout() {

> [!NOTE] SF Symbols are only available on Apple platforms. On Android and web, pass an image with `require()` or a `{ uri }` object instead.

For props and more information, see the [React Navigation integration guide](/docs/guides/usage-with-react-navigation), which documents every accepted `tabBarIcon` source.
> [!NOTE] `unstable_createStandardRouterNavigator` is marked unstable by Expo and may change between minor releases.

### Tint colors

There is no theme in the Expo Router integration, so the tab bar falls back to platform defaults. Pass `tabBarActiveTintColor` and `tabBarInactiveTintColor` on the navigator to set them yourself.

### Custom tab bar

The `tabBar` function receives `state`, `descriptors`, `actions` and `emitter`. If your tab bar needs the router, call `useNavigation()` from `expo-router` inside it.

## Expo SDK 52 to 55

Install `@bottom-tabs/react-navigation`, which provides a native bottom tab navigator for React Navigation.

<PackageManagerTabs command="install @bottom-tabs/react-navigation" />

Create a custom layout adapter using `withLayoutContext` from Expo Router. Put it in `components/bottom-tabs.tsx` and export the navigator, so your layouts can import it:

```tsx
import { withLayoutContext } from 'expo-router';
import {
createNativeBottomTabNavigator,
NativeBottomTabNavigationOptions,
NativeBottomTabNavigationEventMap,
} from '@bottom-tabs/react-navigation';
import { ParamListBase, TabNavigationState } from '@react-navigation/native';

const BottomTabNavigator = createNativeBottomTabNavigator().Navigator;

export const Tabs = withLayoutContext<
NativeBottomTabNavigationOptions,
typeof BottomTabNavigator,
TabNavigationState<ParamListBase>,
NativeBottomTabNavigationEventMap
>(BottomTabNavigator);
```

> [!NOTE] Make sure to provide the correct types for the `withLayoutContext` function. Without it screen options won't have proper types.

The layout itself is the same as above.

### Upgrading to SDK 56 or newer

On SDK 56+ this setup loads a second copy of React Navigation, whose contexts do not line up with the router's.

To upgrade:

1. Replace `@bottom-tabs/react-navigation` with `@bottom-tabs/standard-navigation`.
2. Swap `withLayoutContext` for `unstable_createStandardRouterNavigator` as shown above. The generic type arguments are no longer needed.
3. Uninstall the `@react-navigation/*` packages, and import from `expo-router/react-navigation` instead. That entry point re-exports Expo Router's own copy of React Navigation.

Example: [okwasniewski/ExpoNativeTabs](https://github.com/okwasniewski/ExpoNativeTabs)
## Options and props

For props and more information, see the [React Navigation integration guide](/docs/guides/usage-with-react-navigation), which documents every accepted `tabBarIcon` source. Screen options are identical across both integrations.
58 changes: 58 additions & 0 deletions docs/docs/docs/guides/usage-with-react-navigation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -492,3 +492,61 @@ import { BottomTabBarHeightContext } from 'react-native-bottom-tabs';
)}
</BottomTabBarHeightContext.Consumer>
```

## Using the shared navigator directly

`createNativeBottomTabNavigator` is a thin wrapper over the framework-agnostic navigator in `@bottom-tabs/standard-navigation`. If you need a different router, or want to supply your own props, wire it up yourself:

```tsx
import {
nativeBottomTabsNavigator,
type NativeBottomTabNavigationOptions,
type NativeBottomTabsEventMap,
type NativeBottomTabsNavigatorProps,
} from '@bottom-tabs/standard-navigation';
import {
createStandardNavigationFactories,
TabRouter,
type ParamListBase,
type StandardNavigationTypeBagBase,
type TabActionHelpers,
type TabNavigationState,
type TabRouterOptions,
} from '@react-navigation/native';
import type { ComponentType } from 'react';

interface NativeBottomTabsTypeBag extends StandardNavigationTypeBagBase {
State: TabNavigationState<ParamListBase>;
ScreenOptions: NativeBottomTabNavigationOptions;
EventMap: NativeBottomTabsEventMap;
RouterOptions: TabRouterOptions;
ActionHelpers: TabActionHelpers<ParamListBase>;
Navigator: ComponentType<NativeBottomTabsNavigatorProps>;
}

const { createNavigator } = createStandardNavigationFactories<
NativeBottomTabsTypeBag,
NativeBottomTabsNavigatorProps
>(nativeBottomTabsNavigator, TabRouter);

const Tab = createNavigator();
```

This requires `@react-navigation/native` 7.3.0 or newer.

> [!NOTE] `createStandardNavigationFactories` cannot infer it, so calling it without the type arguments leaves screen options as `{}` and makes every navigator prop a type error. `Navigator` is what carries the navigator props through to `Tab.Navigator`; swap `State`, `RouterOptions` and `ActionHelpers` to match whichever router you pass.

Wiring it yourself skips the theme, so the tab bar falls back to platform defaults. Pass `defaultTintColors` to supply your own fallbacks, either as a prop on `Tab.Navigator` or from a mapper:

```tsx
import { useTheme } from '@react-navigation/native';

const { createNavigator } = createStandardNavigationFactories<
NativeBottomTabsTypeBag,
NativeBottomTabsNavigatorProps
>(nativeBottomTabsNavigator, TabRouter, () => {
const { colors } = useTheme();

return { defaultTintColors: { active: colors.primary } };
});
```
8 changes: 6 additions & 2 deletions packages/example-shared/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ module.exports = function (api) {

const alias = Object.fromEntries(
fs
.readdirSync(packages)
.filter((name) => !name.startsWith('.'))
.readdirSync(packages, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
.map((entry) => entry.name)
.filter((name) =>
fs.existsSync(path.join(packages, name, 'package.json'))
)
.map((name) => {
const pak = require(`../../packages/${name}/package.json`);

Expand Down
13 changes: 11 additions & 2 deletions packages/react-navigation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"!**/.*"
],
"scripts": {
"test": "jest --passWithNoTests",
"test": "jest",
"typecheck": "tsc -b",
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"build": "bob build"
Expand Down Expand Up @@ -57,15 +57,19 @@
},
"devDependencies": {
"@react-navigation/native": "^7.3.0",
"@testing-library/react-native": "^14.0.1",
"@types/color": "^4.2.0",
"@types/jest": "^29.5.5",
"jest": "^29.7.0",
"react": "^19.1.0",
"react-native": "0.81.1",
"react-native-bottom-tabs": "*",
"react-native-builder-bob": "^0.40.13",
"test-renderer": "^1.2.0",
"typescript": "^5.9.2"
},
"dependencies": {
"@bottom-tabs/standard-navigation": "1.4.0",
"color": "^5.0.0"
},
"peerDependencies": {
Expand All @@ -76,9 +80,14 @@
},
"jest": {
"preset": "react-native",
"moduleNameMapper": {
"^@bottom-tabs/standard-navigation$": "<rootDir>/../standard-navigation/src/index.tsx"
},
"modulePathIgnorePatterns": [
"<rootDir>/example/node_modules",
"<rootDir>/lib/"
],
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|@react-native(-community)?|@react-navigation|standard-navigation|color(-convert|-name|-string)?|simple-swizzle)/)"
]
},
"react-native-builder-bob": {
Expand Down
Loading
Loading