Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c3aef33
refactor(navigation-bar): extract module + deprecate Bar alias
burczu Jun 17, 2026
e0da53e
feat(navigation-bar): apply MD3 token-based colors and sizing
burczu Jun 17, 2026
b384ecb
refactor(navigation-bar): remove MD2 shifting mode from the bar
burczu Jun 17, 2026
33bdcb9
feat(navigation-bar): add visible MD3 state layers
burczu Jun 17, 2026
f16775a
feat(navigation-bar): drive motion from tokens and spring the indicator
burczu Jun 17, 2026
c56ceff
feat(navigation-bar): add flexible horizontal variant
burczu Jun 17, 2026
39b2f82
docs(navigation-bar): add example and update JSDoc
burczu Jun 17, 2026
98346d3
fix(navigation-bar): restore labels and active indicator on tab change
burczu Jun 17, 2026
4076ce8
test(navigation-bar): drop redundant shifting test variants
burczu Jun 17, 2026
57d7506
test(navigation-bar): add characterization tests before refactor
burczu Jun 18, 2026
6937a17
refactor(navigation-bar): collapse cross-fade, dedupe item render
burczu Jun 18, 2026
b19e402
refactor(navigation-bar): unify color source on colorRoles
burczu Jun 18, 2026
83b1a8b
docs(navigation-bar): register NavigationBar page, fix docs build
burczu Jun 18, 2026
2057612
refactor(navigation-bar): drop deprecations per review
burczu Jun 18, 2026
8fe9cf7
refactor(navigation-bar): migrate motion to reanimated
burczu Jun 18, 2026
4e9a651
fix(tests): migrate BottomNavigation tests to RNTL v14 patterns
burczu Jul 3, 2026
fb3c953
fix(docs): fix CI build for NavigationBar as top-level component
burczu Jul 3, 2026
c1bf911
refactor(navigation-bar): remove BottomNavigation scene wrapper
burczu Jul 3, 2026
0d49772
Merge branch 'main' of https://github.com/callstack/react-native-pape…
MikitasK Aug 27, 2026
c9fc66a
fix: address MD3 expressive layout issues
MikitasK Aug 27, 2026
e6bd72c
fix: align with M3 tokens and aria semantics
MikitasK Aug 28, 2026
a01e439
Merge branch 'main' of https://github.com/callstack/react-native-pape…
MikitasK Sep 5, 2026
4c0b60a
fix(navigation-bar): type `style` as StyleProp<ViewStyle>
MikitasK Sep 5, 2026
07be96e
fix(navigation-bar): drop the default testID
MikitasK Sep 5, 2026
3281245
docs: document the BottomNavigation removal in the v6 migration guide
MikitasK Sep 5, 2026
b47d0d8
chore: sync navigation bar branch with main
MikitasK Sep 21, 2026
55913bc
fix: update migration docs and tab press API
MikitasK Sep 21, 2026
a92737a
fix: address review feedback on MD3 navigation bar
MikitasK Oct 27, 2026
89298cf
docs: rename the guide & refresh docs data
MikitasK Oct 27, 2026
1a5bf26
style: name pressed route in tab press handlers
MikitasK Oct 27, 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
2 changes: 1 addition & 1 deletion docs/6.x/docs/guides/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"recommended-libraries",
"contributing",
"theming-with-react-navigation",
"bottom-navigation",
"navigation-bar",
"react-navigation",
"rtl",
"ripple-effect",
Expand Down
73 changes: 70 additions & 3 deletions docs/6.x/docs/guides/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ Hardcoded default test IDs have been removed for the components listed below:

- `Appbar.Content`: `appbar-content`
- `Appbar.Header`: `appbar-header`
- `BottomNavigation`: `bottom-navigation`
- `BottomNavigation.Bar`: `bottom-navigation-bar`
- `NavigationBar`: `bottom-navigation` and `bottom-navigation-bar` (v5 `BottomNavigation` and `BottomNavigation.Bar`)
- `Button`: `button`
- `Card`: `card`
- `Chip`: `chip`
Expand All @@ -99,7 +98,6 @@ If you were relying on internal test IDs, update your tests not to rely on inter

Some components now accept explicit `testID` props for their interactable elements:

- `BottomNavigation`: `barTestID` for the internal `BottomNavigation.Bar`, replacing the previous `${testID}-bar` derivation.
- `Chip`: `closeIconTestID` for the close icon button.
- `Dialog` and `Modal`: `overlayTestID` for the overlay displayed behind the content.
- `Menu`: `overlayTestID` for the overlay displayed behind the menu.
Expand All @@ -114,6 +112,75 @@ The `style` props for `Appbar` and `Appbar.Header` no longer accept `Animated.Va

The `style.elevation` property is no longer supported. Use the `elevated` prop to control Appbar elevation.

### BottomNavigation

`BottomNavigation` has been removed. Paper no longer ships a component that owns your tab scenes; the Material 3 navigation bar is now `NavigationBar`, a standalone bar you render next to your own navigator (React Navigation, or your own state).

| v5 | v6 |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `BottomNavigation`, `BottomNavigation.Bar` | `NavigationBar` |
| `BottomNavigationProps`, `BottomNavigationRoute` | `NavigationBarProps`, `NavigationBarRoute` |
| `BottomNavigation.SceneMap`, `renderScene`, `onIndexChange` | render the active scene yourself, alongside the bar |
| `getLazy`, `route.lazy` | removed (the bar no longer owns the scenes) |
| `sceneAnimationEnabled`, `sceneAnimationType`, `sceneAnimationEasing` | removed with the scene container |
| `shifting` | removed (including the active-icon shift and inactive-label transitions) |
| `barStyle` | `style` |
| `animationEasing` | removed (the indicator uses M3 motion from the theme) |
| `barStyle` with `Animated.Value` | `style` with static styles only |
| n/a | new `variant` (`'stacked' \| 'horizontal'`) for the item layout |

```diff
- <BottomNavigation
- navigationState={{ index, routes }}
- onIndexChange={setIndex}
- renderScene={BottomNavigation.SceneMap({ home: HomeScreen, settings: SettingsScreen })}
- barStyle={{ backgroundColor: theme.colors.surfaceContainer }}
- />
+ <>
+ {renderScene({ route: routes[index] })}
+ <NavigationBar
+ navigationState={{ index, routes }}
+ onTabPress={({ route }) => setIndex(routes.findIndex((r) => r.key === route.key))}
+ style={{ backgroundColor: theme.colors.surfaceContainer }}
+ />
+ </>
```

`onTabPress` & `onTabLongPress` callbacks receive only pressed `route`. The v5 event object also carried `preventDefault()` & `defaultPrevented`, so code that reads them now throws `preventDefault is not a function`. The bar never navigates on its own, so navigator's own `tabPress` event decides whether press is handled:

```diff
tabBar={({ navigation, state, descriptors, insets }) => (
<NavigationBar
navigationState={state}
safeAreaInsets={insets}
- onTabPress={({ route, preventDefault }) => {
+ onTabPress={({ route }) => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});

- if (event.defaultPrevented) {
- preventDefault();
- } else {
+ if (!event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(route.name, route.params),
target: state.key,
});
}
}}
/>
)}
```

The `animationEasing` prop is gone along with animation it configured. Active indicator now follows the motion from theme (`theme.motion`) and honors reduce motion preference.

Unlike v5 `barStyle`, the `style` prop no longer accepts `Animated.Value` or `Animated.AnimatedInterpolation`. Pass static styles.

`NavigationBar` also does not set a default `testID`. If your tests looked up `bottom-navigation-bar` (or its `-container` / `-content` / `-content-wrapper` children), pass a `testID` explicitly.

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.

I think we need one more docs change - docs/6.x/docs/guides/bottom-navigation.md still imports BottomNavigation and renders it, which is what this PR removes right? I think we should either rewrite it to include the new NavigationBar or drop it completely and point at at the migration entry.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

done


### Surface

- The `elevation` prop no longer accepts a React Native `Animated.Value`. Any `elevation` changes are animated automatically.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
---
title: Using BottomNavigation with React Navigation
title: Using NavigationBar with React Navigation
---

Build a Material Design bottom tab bar by combining two pieces:

- `@react-navigation/bottom-tabs` handles routing, state, and screen options.
- `BottomNavigation.Bar` renders the Material 3 tab bar (ripple, badges, shifting/labeled modes).

<img src="/react-native-paper/screenshots/material-bottom-tabs.gif" style={{ width: '420px', maxWidth: '100%', margin: '16px 0' }} />
- `NavigationBar` renders the Material 3 navigation bar (state layers, badges, and stacked or horizontal item layouts).

:::info
Install [`@react-navigation/native`](https://reactnavigation.org/docs/getting-started) and [`@react-navigation/bottom-tabs`](https://reactnavigation.org/docs/bottom-tab-navigator) first.
:::

## Quick example

Pass a `BottomNavigation.Bar` to the navigator's `tabBar` prop. The bar reads navigation state and dispatches `tabPress` events back:
Pass a `NavigationBar` to the navigator's `tabBar` prop. The bar reads navigation state and dispatches `tabPress` events back:

```jsx
import MaterialCommunityIcons from '@react-native-vector-icons/material-design-icons';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { CommonActions } from '@react-navigation/native';
import { BottomNavigation } from 'react-native-paper';
import { NavigationBar } from 'react-native-paper';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

const Tab = createBottomTabNavigator();
Expand All @@ -31,20 +29,17 @@ function MyTabs() {

return (
<Tab.Navigator
screenOptions={{ animation: 'shift' }}
tabBar={({ navigation, state, descriptors }) => (
<BottomNavigation.Bar
<NavigationBar
navigationState={state}
safeAreaInsets={insets}
onTabPress={({ route, preventDefault }) => {
onTabPress={({ route }) => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (event.defaultPrevented) {
preventDefault();
} else {
if (!event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(route.name, route.params),
target: state.key,
Expand Down
2 changes: 1 addition & 1 deletion docs/6.x/docs/guides/theming.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ Now you can use your `FancyButton` component everywhere instead of using `Button
## Dark Theme

Since 3.0 we adapt dark theme to follow [Material design guidelines](https://material.io/design/color/dark-theme.html). <br/>
In contrast to light theme, dark theme by default uses `surface` colour instead of `primary` on large components like `Appbar` or `BottomNavigation`.<br/>
In contrast to light theme, dark theme by default uses `surface` colour instead of `primary` on large components like `Appbar` or `NavigationBar`.<br/>
The dark theme adds a white overlay with opacity depending on elevation of surfaces. It uses it for the better accentuation of surface elevation. Using only shadow is highly imperceptible on dark surfaces.

If you don't use a custom theme, Paper will automatically change between the default theme and the default dark theme, depending on device settings.
Expand Down
7 changes: 3 additions & 4 deletions docs/component-docs.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ const pages = {
},
Badge: 'Badge',
Banner: 'Banner',
BottomNavigation: {
BottomNavigation: 'BottomNavigation/BottomNavigation',
BottomNavigationBar: 'BottomNavigation/BottomNavigationBar',
},
Button: {
Button: 'Button/Button',
},
Expand Down Expand Up @@ -115,6 +111,9 @@ const pages = {
MenuItem: 'Menu/MenuItem',
},
Modal: 'Modal',
NavigationBar: {
NavigationBar: 'NavigationBar/NavigationBar',
},
Comment on lines +114 to +116

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The page title is now NavigationBar, but docs/src/data/extendedExamples.ts still keys the "with React Navigation" example as 'BottomNavigation.Bar', and docs/src/data/screenshots.ts still has BottomNavigation and 'BottomNavigation.Bar' entries. render.ts looks both up by doc.title, so the NavigationBar page renders without the React Navigation example that this PR updates, and without a screenshot. Rename the extended example key to NavigationBar and replace the old screenshot entries. (via Claude, verify if it makes sense)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

done 👌 both keys are NavigationBar now and built page renders the screenshot & React Navigation example

Portal: {
Portal: {
source: 'Portal/Portal',
Expand Down
Binary file added docs/public/screenshots/navigation-bar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 2 additions & 5 deletions docs/src/data/extendedExamples.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import {
dynamicCode,
staticCode,
} from './extendedExamples/BottomNavigationBar.ts';
import { dynamicCode, staticCode } from './extendedExamples/NavigationBar.ts';

export const extendedExamples = {
'BottomNavigation.Bar': {
NavigationBar: {
'with React Navigation': {
static: staticCode,
dynamic: dynamicCode,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const staticCode = `import { Text, View } from 'react-native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Provider, BottomNavigation } from 'react-native-paper';
import { Provider, NavigationBar } from 'react-native-paper';
import MaterialCommunityIcons from '@react-native-vector-icons/material-design-icons';
import {
CommonActions,
Expand All @@ -24,23 +24,18 @@ function SettingsScreen() {
}

const MyTabs = createBottomTabNavigator({
screenOptions: {
animation: 'shift',
},
tabBar: ({ navigation, state, descriptors, insets }) => (
<BottomNavigation.Bar
<NavigationBar
navigationState={state}
safeAreaInsets={insets}
onTabPress={({ route, preventDefault }) => {
onTabPress={({ route }) => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});

if (event.defaultPrevented) {
preventDefault();
} else {
if (!event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(route.name, route.params),
target: state.key,
Expand Down Expand Up @@ -102,7 +97,7 @@ export default function App() {
export const dynamicCode = `import { Text, View } from 'react-native';
import { NavigationContainer, CommonActions } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Provider, BottomNavigation } from 'react-native-paper';
import { Provider, NavigationBar } from 'react-native-paper';
import MaterialCommunityIcons from '@react-native-vector-icons/material-design-icons';

function HomeScreen() {
Expand All @@ -128,23 +123,18 @@ export default function App() {
<Provider>
<NavigationContainer>
<Tab.Navigator
screenOptions={{
animation: 'shift',
}}
tabBar={({ navigation, state, descriptors, insets }) => (
<BottomNavigation.Bar
<NavigationBar
navigationState={state}
safeAreaInsets={insets}
onTabPress={({ route, preventDefault }) => {
onTabPress={({ route }) => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});

if (event.defaultPrevented) {
preventDefault();
} else {
if (!event.defaultPrevented) {
navigation.dispatch({
...CommonActions.navigate(route.name, route.params),
target: state.key,
Expand Down
3 changes: 1 addition & 2 deletions docs/src/data/screenshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ export const screenshots = {
dot: 'screenshots/badge-3.png',
},
Banner: 'screenshots/banner.gif',
BottomNavigation: 'screenshots/bottom-navigation.gif',
'BottomNavigation.Bar': 'screenshots/bottom-navigation-tabs.jpg',
Button: {
text: 'screenshots/button-1.png',
outlined: 'screenshots/button-2.png',
Expand Down Expand Up @@ -105,6 +103,7 @@ export const screenshots = {
},
'Menu.Item': 'screenshots/menu-item.png',
Modal: 'screenshots/modal.gif',
NavigationBar: 'screenshots/navigation-bar.png',
ProgressBar: 'screenshots/progress-bar.png',
RadioButton: {
'Android (enabled)': 'screenshots/radio-enabled.android.png',
Expand Down
4 changes: 2 additions & 2 deletions example/src/ExampleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import AvatarExample from './Examples/AvatarExample';
import BadgeExample from './Examples/BadgeExample';
import BannerExample from './Examples/BannerExample';
import BottomNavigationBarExample from './Examples/BottomNavigationBarExample';
import BottomNavigationExample from './Examples/BottomNavigationExample';
import ButtonExample from './Examples/ButtonExample';
import CardExample from './Examples/CardExample';
import CheckboxExample from './Examples/CheckboxExample';
Expand All @@ -27,6 +26,7 @@ import ListAccordionExampleGroup from './Examples/ListAccordionGroupExample';
import ListItemExample from './Examples/ListItemExample';
import ListSectionExample from './Examples/ListSectionExample';
import MenuExample from './Examples/MenuExample';
import NavigationBarExample from './Examples/NavigationBarExample';
import ProgressBarExample from './Examples/ProgressBarExample';
import RadioButtonExample from './Examples/RadioButtonExample';
import RadioButtonGroupExample from './Examples/RadioButtonGroupExample';
Expand Down Expand Up @@ -54,7 +54,6 @@ export const mainExamples = {
Badge: BadgeExample,
Banner: BannerExample,
BottomNavigationBarExample,
BottomNavigation: BottomNavigationExample,
Button: ButtonExample,
Card: CardExample,
Checkbox: CheckboxExample,
Expand All @@ -71,6 +70,7 @@ export const mainExamples = {
ListSection: ListSectionExample,
ListItem: ListItemExample,
Menu: MenuExample,
NavigationBar: NavigationBarExample,
Progressbar: ProgressBarExample,
Radio: RadioButtonExample,
RadioGroup: RadioButtonGroupExample,
Expand Down
12 changes: 5 additions & 7 deletions example/src/Examples/BottomNavigationBarExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
SFSymbol,
MaterialSymbol,
} from '@react-navigation/native';
import { Text, BottomNavigation } from 'react-native-paper';
import { Text, NavigationBar } from 'react-native-paper';

function HomeScreen() {
return (
Expand All @@ -34,18 +34,16 @@ const BottomNavigationBarExample = createBottomTabNavigator({
headerShown: false,
},
tabBar: ({ navigation, state, descriptors }) => (
<BottomNavigation.Bar
<NavigationBar
navigationState={state}
onTabPress={({ route, preventDefault }) => {
onTabPress={({ route }) => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});

if (event.defaultPrevented) {
preventDefault();
} else {
if (!event.defaultPrevented) {
// Custom tab bars must target the tab navigator state.
navigation.dispatch({
...CommonActions.navigate(route.name, route.params),
Expand Down Expand Up @@ -119,7 +117,7 @@ const BottomNavigationBarExample = createBottomTabNavigator({
});

export default Object.assign(BottomNavigationBarExample, {
title: 'Bottom Navigation Bar',
title: 'Navigation Bar (React Navigation)',
});

const styles = StyleSheet.create({
Expand Down
Loading