Customizing Tabs
The default tab bar works well out of the box, but most apps need to match their brand. React Navigation gives you full control — from simple color changes to building an entirely custom tab bar component.
Tab Bar Colors
Set active and inactive colors through screenOptions:
<Tab.Navigator
screenOptions={{
tabBarActiveTintColor: '#6200ee',
tabBarInactiveTintColor: '#999',
}}
>
tabBarActiveTintColor— the color of the icon and label for the selected tabtabBarInactiveTintColor— the color for unselected tabs
Tab Bar Background and Style
Style the tab bar container itself:
<Tab.Navigator
screenOptions={{
tabBarStyle: {
backgroundColor: '#1a1a2e',
borderTopWidth: 0,
height: 60,
paddingBottom: 8,
},
tabBarActiveTintColor: '#e94560',
tabBarInactiveTintColor: '#666',
}}
>
tabBarStyle accepts any ViewStyle properties. Common adjustments include background color, height, removing the top border, and adjusting padding.
Hiding the Tab Bar on Specific Screens
When you nest a stack inside a tab, you often want to hide the tab bar on detail screens. Use tabBarStyle with display: 'none' through options:
<Tab.Screen
name="HomeStack"
component={HomeStackNavigator}
options={({ route }) => {
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Home';
return {
tabBarStyle: routeName === 'Details' ? { display: 'none' } : undefined,
};
}}
/>
Import getFocusedRouteNameFromRoute from @react-navigation/native to check which screen is active inside a nested navigator.
Custom Tab Bar Labels
Control label visibility and text:
<Tab.Navigator
screenOptions={{
tabBarShowLabel: false, // hide all labels, show only icons
}}
>
Or customize per-screen:
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{ tabBarLabel: 'Me' }}
/>
Building a Custom Tab Bar
For full control, replace the entire tab bar with your own component using tabBar:
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
function CustomTabBar({ state, descriptors, navigation }) {
return (
<View style={tabStyles.container}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label = options.tabBarLabel ?? options.title ?? route.name;
const isFocused = state.index === index;
const onPress = () => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};
return (
<TouchableOpacity
key={route.key}
onPress={onPress}
style={[tabStyles.tab, isFocused && tabStyles.activeTab]}
>
<Text style={[tabStyles.label, isFocused && tabStyles.activeLabel]}>
{label}
</Text>
</TouchableOpacity>
);
})}
</View>
);
}
const tabStyles = StyleSheet.create({
container: {
flexDirection: 'row',
backgroundColor: '#fff',
paddingBottom: 20,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: '#eee',
},
tab: { flex: 1, alignItems: 'center' },
activeTab: { borderTopWidth: 2, borderTopColor: '#6200ee' },
label: { fontSize: 12, color: '#999' },
activeLabel: { color: '#6200ee', fontWeight: 'bold' },
});
Wire it into the navigator:
<Tab.Navigator tabBar={(props) => <CustomTabBar {...props} />}>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Search" component={SearchScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
The custom tab bar receives state (current navigation state), descriptors (screen options), and navigation (the navigation object). You loop over the routes, check which is focused, and handle presses by calling navigation.navigate. This gives you complete control over layout, animation, and behavior.
In the next lesson we'll look at the Drawer navigator — a slide-out menu panel.