Skill: Material Top Tabs
Description
Use createMaterialTopTabNavigator for swipeable top tabs with a Material-style tab bar.
When to Use
- Building segmented content that should switch by tap or horizontal swipe
- Having a scrollable tab bar at the top of the screen
Prerequisites
Install react-native-pager-view for material top tabs to work on native platforms.
Basic Example
Static API
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const HomeTabs = createMaterialTopTabNavigator({
screens: {
Feed: FeedScreen,
Updates: UpdatesScreen,
},
});Dynamic API
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
function HomeTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Updates" component={UpdatesScreen} />
</Tab.Navigator>
);
}Common Features
Displaying Icons
Icons are hidden by default. Set tabBarShowIcon: true, then provide tabBarIcon. Optionally use tabBarActiveTintColor and tabBarInactiveTintColor to change the color passed to the icon.
Static API
import Ionicons from '@expo/vector-icons/Ionicons';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const HomeTabs = createMaterialTopTabNavigator({
screenOptions: ({ route }) => ({
tabBarShowIcon: true,
tabBarIcon: ({ focused, color }) => {
let iconName;
switch (route.name) {
case 'Feed':
iconName = focused ? 'newspaper' : 'newspaper-outline';
break;
default:
iconName = focused ? 'notifications' : 'notifications-outline';
break;
}
return <Ionicons name={iconName} size={18} color={color} />;
},
tabBarActiveTintColor: 'tomato',
tabBarInactiveTintColor: 'gray',
}),
screens: {
Feed: FeedScreen,
Updates: UpdatesScreen,
},
});Dynamic API
import Ionicons from '@expo/vector-icons/Ionicons';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
function HomeTabs() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarShowIcon: true,
tabBarIcon: ({ focused, color }) => {
let iconName;
switch (route.name) {
case 'Feed':
iconName = focused ? 'newspaper' : 'newspaper-outline';
break;
default:
iconName = focused ? 'notifications' : 'notifications-outline';
break;
}
return <Ionicons name={iconName} size={18} color={color} />;
},
tabBarActiveTintColor: 'tomato',
tabBarInactiveTintColor: 'gray',
})}
>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Updates" component={UpdatesScreen} />
</Tab.Navigator>
);
}The tabBarIcon option can render any React element. Choose between the icon libraries that best fit the app's design and environment:
@expo/vector-iconsif Expo SDK is configured in the appreact-native-vector-iconsfor an app using React Native Community CLI<Image>for local images
Scrollable Tab Bar
Use tabBarScrollEnabled: true if the list of tabs won't fit on the screen to make the tab bar scrollable. Set a fixed width for each tab with tabBarItemStyle to make the scrollable tab bar work better.
Use tabBarItemStyle with { width: 'auto' } if the tab button width should fit the label instead of being equally divided.
Static API
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const HomeTabs = createMaterialTopTabNavigator({
screenOptions: {
tabBarScrollEnabled: true,
},
screens: {
Feed: FeedScreen,
Updates: UpdatesScreen,
Mentions: MentionsScreen,
Saved: SavedScreen,
Archive: ArchiveScreen,
},
});Dynamic API
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
function HomeTabs() {
return (
<Tab.Navigator
screenOptions={{
tabBarScrollEnabled: true,
}}
>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Updates" component={UpdatesScreen} />
<Tab.Screen name="Mentions" component={MentionsScreen} />
<Tab.Screen name="Saved" component={SavedScreen} />
<Tab.Screen name="Archive" component={ArchiveScreen} />
</Tab.Navigator>
);
}Badges
Use tabBarBadge to render a custom badge element for a tab.
Static API
import { Text, View } from 'react-native';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const HomeTabs = createMaterialTopTabNavigator({
screens: {
Inbox: {
screen: InboxScreen,
options: {
tabBarBadge: () => (
<View
style={{
minWidth: 18,
height: 18,
borderRadius: 9,
backgroundColor: 'tomato',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text style={{ color: 'white', fontSize: 12 }}>3</Text>
</View>
),
},
},
Settings: SettingsScreen,
},
});Dynamic API
import { Text, View } from 'react-native';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
function HomeTabs() {
return (
<Tab.Navigator>
<Tab.Screen
name="Inbox"
component={InboxScreen}
options={{
tabBarBadge: () => (
<View
style={{
minWidth: 18,
height: 18,
borderRadius: 9,
backgroundColor: 'tomato',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text style={{ color: 'white', fontSize: 12 }}>3</Text>
</View>
),
}}
/>
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
);
}Indicators
Use tabBarIndicatorStyle to customize the indicator:
- Use horizontal margin (
{ marginHorizontal: 16 }) to make the indicator shorter than the tab button width. - Set width (
{ width: 24 }) for a fixed-width indicator, withmarginHorizontal: 'auto'to center it under the tab button. - Don't set
borderRadiusiftabBarItemStylehas'auto'width as it's not supported.
If the default styling options aren't sufficient, use tabBarIndicator to render a custom indicator.
Static API
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const HomeTabs = createMaterialTopTabNavigator({
screenOptions: {
tabBarIndicatorStyle: {
backgroundColor: '#111827',
height: 3,
marginHorizontal: 16,
},
},
screens: {
Feed: FeedScreen,
Updates: UpdatesScreen,
},
});Dynamic API
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
function HomeTabs() {
return (
<Tab.Navigator
screenOptions={{
tabBarIndicatorStyle: {
backgroundColor: '#111827',
height: 3,
marginHorizontal: 16,
},
}}
>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Updates" component={UpdatesScreen} />
</Tab.Navigator>
);
}Custom Tab Bar
Use the tabBar option if a custom design is needed. Use the navigation prop passed to the tab bar instead of useNavigation.
import { Text, View } from 'react-native';
import { useLinkBuilder } from '@react-navigation/native';
import { PlatformPressable } from '@react-navigation/elements';
function MyTabBar({ state, descriptors, navigation }) {
const { buildHref } = useLinkBuilder();
return (
<View style={{ flexDirection: 'row' }}>
{state.routes.map((route, index) => {
const { options } = descriptors[route.key];
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
const isFocused = state.index === index;
return (
<PlatformPressable
key={route.key}
href={buildHref(route.name, route.params)}
onPress={() => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name, route.params);
}
}}
onLongPress={() =>
navigation.emit({
type: 'tabLongPress',
target: route.key,
})
}
style={{ flex: 1, alignItems: 'center', paddingVertical: 12 }}
>
<Text style={{ opacity: isFocused ? 1 : 0.6 }}>{label}</Text>
</PlatformPressable>
);
})}
</View>
);
}Static API
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const HomeTabs = createMaterialTopTabNavigator({
tabBar: (props) => <MyTabBar {...props} />,
screens: {
Feed: FeedScreen,
Updates: UpdatesScreen,
},
});Dynamic API
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
function HomeTabs() {
return (
<Tab.Navigator tabBar={(props) => <MyTabBar {...props} />}>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Updates" component={UpdatesScreen} />
</Tab.Navigator>
);
}If the custom tab bar also needs proper web links, use useLinkBuilder when building each tab button.
Lazy Rendering
All screens are mounted immediately by default for smoother swiping. Enable lazy only when rendering tabs upfront is too expensive.
Static API
import { ActivityIndicator, View } from 'react-native';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const HomeTabs = createMaterialTopTabNavigator({
screenOptions: {
lazy: true,
},
screens: {
Feed: FeedScreen,
Updates: UpdatesScreen,
Mentions: MentionsScreen,
},
});Dynamic API
import { ActivityIndicator, View } from 'react-native';
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
const Tab = createMaterialTopTabNavigator();
function HomeTabs() {
return (
<Tab.Navigator
screenOptions={{
lazy: true,
}}
>
<Tab.Screen name="Feed" component={FeedScreen} />
<Tab.Screen name="Updates" component={UpdatesScreen} />
<Tab.Screen name="Mentions" component={MentionsScreen} />
</Tab.Navigator>
);
}Customize lazy loading behavior with lazyPlaceholder to show a custom placeholder before the screen is loaded and lazyPreloadDistance to control how many screens away from the focused screen should be preloaded.