Skip to content

Commit fa5546c

Browse files
committed
Rework bottom tabs guide
1 parent d4e1717 commit fa5546c

File tree

3 files changed

+107
-186
lines changed

3 files changed

+107
-186
lines changed

versioned_docs/version-8.x/bottom-tab-navigator.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,9 @@ The icon can be of following types with `native` implementation:
576576
}
577577
```
578578

579-
On iOS, you can additionally pass a `tinted` property to control whether the icon should be tinted with the active/inactive color:
579+
It's necessary to provide icons for multiple screen densities (1x, 2x, 3x), e.g.: `icon.png`, `[email protected]`, `[email protected]` etc. as icons are not scaled automatically on iOS for the `native` implementation.
580+
581+
A `tinted` property can be used to control whether the icon should be tinted with the active/inactive color:
580582

581583
```js
582584
tabBarIcon: {
@@ -586,7 +588,9 @@ The icon can be of following types with `native` implementation:
586588
}
587589
```
588590

589-
The image is tinted by default.
591+
Set `tinted` to `false` if the image has its own colors that you want to preserve.
592+
593+
The image is tinted by default. Overriding is only supported on iOS for the `native` implementation, all platforms for the `custom` implementation.
590594

591595
- [SF Symbols](https://developer.apple.com/sf-symbols/) name - Supported on iOS
592596

@@ -617,7 +621,7 @@ tabBarIcon: ({ focused }) => {
617621
},
618622
```
619623

620-
This is only supported on iOS. On Android, the icon specified for inactive state will be used for both active and inactive states.
624+
This not supported on Android with `native` implementation, the icon specified for inactive state will be used for both active and inactive states.
621625

622626
To provide different icons for different platforms, you can use [`Platform.select`](https://reactnative.dev/docs/platform-specific-code):
623627

versioned_docs/version-8.x/customizing-bottom-tabs.md

Lines changed: 92 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,17 @@ This guide covers customizing the tab bar in [`createBottomTabNavigator`](bottom
1313

1414
This is similar to how you would customize a stack navigator — there are some properties that are set when you initialize the tab navigator and others that can be customized per-screen in `options`.
1515

16-
<Tabs groupId="config" queryString="config">
17-
<TabItem value="static" label="Static" default>
16+
Icons can be specified using the [`tabBarIcon`](bottom-tab-navigator.md#tabbaricon) option. The format of the icon varies based on the platform:
1817

19-
```js name="Tab bar icons" snack dependencies=@expo/vector-icons,@expo/vector-icons/Ionicons
18+
- Local image - all platforms
19+
- SF Symbols name - iOS
20+
- Custom drawable name - Android
21+
22+
```js name="Tab bar icons" static2dynamic
2023
import * as React from 'react';
2124
import { View, Text } from 'react-native';
2225
import { createStaticNavigation } from '@react-navigation/native';
2326
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
24-
// codeblock-focus-start
25-
// You can import Ionicons from @expo/vector-icons/Ionicons if you use Expo or
26-
// react-native-vector-icons/Ionicons otherwise.
27-
import Ionicons from 'react-native-vector-icons/Ionicons';
28-
29-
// codeblock-focus-end
3027

3128
function HomeScreen() {
3229
return (
@@ -46,29 +43,45 @@ function SettingsScreen() {
4643

4744
// codeblock-focus-start
4845
const RootTabs = createBottomTabNavigator({
49-
screenOptions: ({ route }) => ({
50-
// highlight-start
51-
tabBarIcon: ({ focused, color, size }) => {
52-
let iconName;
53-
54-
if (route.name === 'Home') {
55-
iconName = focused
56-
? 'ios-information-circle'
57-
: 'ios-information-circle-outline';
58-
} else if (route.name === 'Settings') {
59-
iconName = focused ? 'ios-list' : 'ios-list-outline';
60-
}
61-
62-
// You can return any component that you like here!
63-
return <Ionicons name={iconName} size={size} color={color} />;
64-
},
65-
// highlight-end
46+
screenOptions: {
6647
tabBarActiveTintColor: 'tomato',
6748
tabBarInactiveTintColor: 'gray',
68-
}),
49+
},
6950
screens: {
70-
Home: HomeScreen,
71-
Settings: SettingsScreen,
51+
Home: createBottomTabScreen({
52+
screen: HomeScreen,
53+
options: {
54+
// highlight-start
55+
tabBarIcon: Platform.select({
56+
ios: {
57+
type: 'sfSymbol',
58+
name: 'house',
59+
},
60+
default: {
61+
type: 'image',
62+
source: require('./path/to/home-icon.png'),
63+
},
64+
}),
65+
// highlight-end
66+
},
67+
}),
68+
Settings: createBottomTabScreen({
69+
screen: SettingsScreen,
70+
options: {
71+
// highlight-start
72+
tabBarIcon: Platform.select({
73+
ios: {
74+
type: 'sfSymbol',
75+
name: 'gear',
76+
},
77+
default: {
78+
type: 'image',
79+
source: require('./path/to/settings-icon.png'),
80+
},
81+
}),
82+
// highlight-end
83+
},
84+
}),
7285
},
7386
});
7487
// codeblock-focus-end
@@ -80,97 +93,57 @@ export default function App() {
8093
}
8194
```
8295

83-
</TabItem>
84-
<TabItem value="dynamic" label="Dynamic">
96+
Let's dissect this:
8597

86-
```js name="Tab based navigation" snack dependencies=@expo/vector-icons,@expo/vector-icons/Ionicons
87-
import * as React from 'react';
88-
import { Text, View } from 'react-native';
89-
import { NavigationContainer } from '@react-navigation/native';
90-
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
91-
// codeblock-focus-start
92-
// You can import Ionicons from @expo/vector-icons/Ionicons if you use Expo or
93-
// react-native-vector-icons/Ionicons otherwise.
94-
import Ionicons from 'react-native-vector-icons/Ionicons';
98+
- [`tabBarIcon`](bottom-tab-navigator.md#tabbaricon) is a supported option in bottom tab navigator. So we know we can use it on our screen components in the `options` prop.
99+
- `tabBarIcon` is an object specifying the icon to display.
100+
- For iOS, you can use SF Symbols by setting `type: 'sfSymbol'` and providing the symbol `name`.
101+
- For other platforms, use `type: 'image'` with a `source` pointing to your image file. Image files must be provided for multiple screen densities (1x, 2x, 3x), e.g.: `home-icon.png`, `[email protected]`, `[email protected]`.
102+
- [`Platform.select`](https://reactnative.dev/docs/platform#select) can be used to provide different icons based on the platform.
103+
- The `tabBarActiveTintColor` and `tabBarInactiveTintColor` options in `screenOptions` control the icon and label colors. These default to the iOS platform defaults, but you can change them as shown above.
104+
- Read the [full API reference](bottom-tab-navigator.md) for further information on `createBottomTabNavigator` configuration options.
95105

96-
// codeblock-focus-end
106+
### Different icons for active and inactive states
97107

98-
function HomeScreen() {
99-
return (
100-
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
101-
<Text>Home!</Text>
102-
</View>
103-
);
104-
}
108+
You can also provide different icons for active and inactive states by using a function for the `tabBarIcon` option:
105109

106-
function SettingsScreen() {
107-
return (
108-
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
109-
<Text>Settings!</Text>
110-
</View>
111-
);
112-
}
110+
```js
111+
tabBarIcon: ({ focused }) => {
112+
return {
113+
type: 'image',
114+
source: focused
115+
? require('./path/to/home-filled-icon.png')
116+
: require('./path/to/home-outline-icon.png'),
117+
};
118+
},
119+
```
113120

114-
const Tab = createBottomTabNavigator();
121+
The `focused` parameter indicates whether the tab is active or inactive.
115122

116-
// codeblock-focus-start
117-
function RootTabs() {
118-
return (
119-
<Tab.Navigator
120-
screenOptions={({ route }) => ({
121-
// highlight-start
122-
tabBarIcon: ({ focused, color, size }) => {
123-
let iconName;
124-
125-
if (route.name === 'Home') {
126-
iconName = focused
127-
? 'ios-information-circle'
128-
: 'ios-information-circle-outline';
129-
} else if (route.name === 'Settings') {
130-
iconName = focused ? 'ios-list' : 'ios-list-outline';
131-
}
132-
133-
// You can return any component that you like here!
134-
return <Ionicons name={iconName} size={size} color={color} />;
135-
},
136-
// highlight-end
137-
tabBarActiveTintColor: 'tomato',
138-
tabBarInactiveTintColor: 'gray',
139-
})}
140-
>
141-
<Tab.Screen name="Home" component={HomeScreen} />
142-
<Tab.Screen name="Settings" component={SettingsScreen} />
143-
</Tab.Navigator>
144-
);
145-
}
146-
// codeblock-focus-end
123+
This not supported on Android with `native` [implementation](bottom-tab-navigator.md#implementation), the icon specified for inactive state will be used for both active and inactive states.
147124

148-
export default function App() {
149-
return (
150-
<NavigationContainer>
151-
<RootTabs />
152-
</NavigationContainer>
153-
);
154-
}
155-
```
125+
### Using [React Native Vector Icons](https://github.com/oblador/react-native-vector-icons)
156126

157-
</TabItem>
158-
</Tabs>
127+
The React Native Vector Icons library provides a large set of icons. To use vector icons in your tab bar, we'd need to [get an image source](https://github.com/oblador/react-native-vector-icons?tab=readme-ov-file#usage-as-png-imagesource-object) from the icon component.
159128

160-
Let's dissect this:
129+
First, make sure to install the appropriate icon package (e.g. `@react-native-vector-icons/material-design-icons`) and `@react-native-vector-icons/get-image` and rebuild the app after installation. Then, you can use the `getImageSourceSync` method to get the image source for the desired icon:
161130

162-
- `tabBarIcon` is a supported option in bottom tab navigator. So we know we can use it on our screen components in the `options` prop, but in this case chose to put it in the `screenOptions` prop of `Tab.Navigator` in order to centralize the icon configuration for convenience.
163-
- `tabBarIcon` is a function that is given the `focused` state, `color`, and `size` params. If you take a peek further down in the configuration you will see `tabBarActiveTintColor` and `tabBarInactiveTintColor`. These default to the iOS platform defaults, but you can change them here. The `color` that is passed through to the `tabBarIcon` is either the active or inactive one, depending on the `focused` state (focused is active). The `size` is the size of the icon expected by the tab bar.
164-
- Read the [full API reference](bottom-tab-navigator.md) for further information on `createBottomTabNavigator` configuration options.
131+
```js
132+
import { MaterialDesignIcons } from '@react-native-vector-icons/material-design-icons';
165133

166-
## Add badges to icons
134+
// ...
167135

168-
Sometimes we want to add badges to some icons. You can use the [`tabBarBadge` option](bottom-tab-navigator.md#tabbarbadge) to do it:
136+
tabBarIcon: {
137+
type: 'image',
138+
source: MaterialDesignIcons.getImageSourceSync('home', 22),
139+
},
140+
```
169141

170-
<Tabs groupId="config" queryString="config">
171-
<TabItem value="static" label="Static" default>
142+
## Add badges to tab items
172143

173-
```js name="Tab based navigation" snack
144+
Sometimes we want to add badges to some tabs. You can use the [`tabBarBadge` option](bottom-tab-navigator.md#tabbarbadge) to do it:
145+
146+
```js name="Tab based navigation" static2dynamic
174147
import * as React from 'react';
175148
import { View, Text } from 'react-native';
176149
import { createStaticNavigation } from '@react-navigation/native';
@@ -195,15 +168,17 @@ function SettingsScreen() {
195168
// codeblock-focus-start
196169
const RootTabs = createBottomTabNavigator({
197170
screens: {
198-
Home: {
171+
Home: createBottomTabScreen({
199172
screen: HomeScreen,
200173
options: {
201174
// highlight-start
202175
tabBarBadge: 3,
203176
// highlight-end
204177
},
205-
},
206-
Settings: SettingsScreen,
178+
}),
179+
Settings: createBottomTabScreen({
180+
screen: SettingsScreen,
181+
}),
207182
},
208183
});
209184
// codeblock-focus-end
@@ -215,66 +190,18 @@ export default function App() {
215190
}
216191
```
217192

218-
</TabItem>
219-
<TabItem value="dynamic" label="Dynamic">
220-
221-
```js name="Tab based navigation" snack
222-
import * as React from 'react';
223-
import { Text, View } from 'react-native';
224-
import { NavigationContainer } from '@react-navigation/native';
225-
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
193+
From UI perspective this component is ready to use, but you still need to find some way to pass down the badge count properly from somewhere else, like using [React Context](https://react.dev/reference/react/use):
226194

227-
function HomeScreen() {
228-
return (
229-
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
230-
<Text>Home!</Text>
231-
</View>
232-
);
233-
}
234-
235-
function SettingsScreen() {
236-
return (
237-
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
238-
<Text>Settings!</Text>
239-
</View>
240-
);
241-
}
242-
243-
const Tab = createBottomTabNavigator();
244-
245-
// codeblock-focus-start
246-
function RootTabs() {
247-
return (
248-
<Tab.Navigator>
249-
<Tab.Screen
250-
name="Home"
251-
component={HomeScreen}
252-
options={{
253-
// highlight-start
254-
tabBarBadge: 3,
255-
// highlight-end
256-
}}
257-
/>
258-
<Tab.Screen name="Settings" component={SettingsScreen} />
259-
</Tab.Navigator>
260-
);
261-
}
262-
// codeblock-focus-end
195+
```js
196+
options: () => {
197+
const unreadMessagesCount = use(UnreadMessagesCountContext);
263198

264-
export default function App() {
265-
return (
266-
<NavigationContainer>
267-
<RootTabs />
268-
</NavigationContainer>
269-
);
270-
}
199+
return {
200+
tabBarBadge: unreadMessagesCount,
201+
};
202+
};
271203
```
272204

273-
</TabItem>
274-
</Tabs>
275-
276-
From UI perspective this component is ready to use, but you still need to find some way to pass down the badge count properly from somewhere else, like using [React Context](https://react.dev/reference/react/useContext), [Redux](https://redux.js.org/), [MobX](https://mobx.js.org/) or [event emitters](https://github.com/facebook/react-native/blob/master/Libraries/vendor/emitter/EventEmitter.js).
277-
278205
You can also update the badge from within the screen component by using the `setOptions` method:
279206

280207
```js

versioned_docs/version-8.x/screen-options.md

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -311,34 +311,24 @@ const RootStack = createNativeStackNavigator({
311311

312312
Similar to `options`, you can also pass a function to `screenOptions`. The function will receive the [`navigation` object](navigation-object.md) and the [`route` object](route-object.md) for each screen. This can be useful if you want to configure options for all the screens in one place based on the route:
313313

314-
```js name="Screen options for tab navigator" snack dependencies=@expo/vector-icons static2dynamic
314+
```js name="Screen options for tab navigator" snack static2dynamic
315315
import * as React from 'react';
316316
import { View } from 'react-native';
317317
import { createStaticNavigation } from '@react-navigation/native';
318318
import {
319319
createBottomTabNavigator,
320320
createBottomTabScreen,
321321
} from '@react-navigation/bottom-tabs';
322-
import { MaterialCommunityIcons } from '@expo/vector-icons';
323322

324323
// codeblock-focus-start
325324
const MyTabs = createBottomTabNavigator({
326-
screenOptions: ({ route }) => ({
327-
tabBarIcon: ({ color, size }) => {
328-
const icons = {
329-
Home: 'home',
330-
Profile: 'account',
331-
};
332-
333-
return (
334-
<MaterialCommunityIcons
335-
name={icons[route.name]}
336-
color={color}
337-
size={size}
338-
/>
339-
);
340-
},
341-
}),
325+
screenOptions: ({ route }) => {
326+
const title = route.name === 'Home' ? 'Welcome' : `${route.name} screen`;
327+
328+
return {
329+
headerTitle: title,
330+
};
331+
},
342332
screens: {
343333
Home: createBottomTabScreen({
344334
screen: EmptyScreen,

0 commit comments

Comments
 (0)