Skip to main content

Bottom tabs

Let's add a bottom tabs navigator with two tabs.

With Expo Router, every file in the app folder is a screen for your app, and the _layout files describe how the app should be laid out.

Add a second screen​

Add profile screen:

New file: app/profile.tsx
import { Text, View, StyleSheet } from "react-native";

export default function ProfileScreen() {
return (
<View style={styles.container}>
<Text style={styles.text}>Profile</Text>
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#fff",
},
text: {
fontSize: 24,
},
});

Two-tab layout​

Now we'll want to update the layout file to list the second screen and render as Tabs instead of a Stack:

Update: app/_layout.tsx
@@ -1,9 +1,10 @@
-import { Stack } from "expo-router";
+import { Tabs } from "expo-router";

export default function Layout() {
return (
- <Stack>
- <Stack.Screen name="index" options={{ title: "Home" }} />
- </Stack>
+ <Tabs>
+ <Tabs.Screen name="index" options={{ title: "Home" }} />
+ <Tabs.Screen name="profile" options={{ title: "Profile" }} />
+ </Tabs>
);
}

Bottom tabs icons​

We'll want to add some icons for our tabs. These can be any React component, so you can bring your own images SVGs etc. For this app, we'll use @expo/vector-icons.

Install the expo vector icons library:

npx expo install @expo/vector-icons

You can browse the available icons here and choose what you like (I chose this leaf and this user icon). We'll also pass in #29b365 as the active tint color for all the tab icons.

Update: app/_layout.tsx
@@ -1,10 +1,31 @@
import { Tabs } from "expo-router";
+import Entypo from "@expo/vector-icons/Entypo";
+import Feather from "@expo/vector-icons/Feather";
+import { theme } from "../theme";

export default function Layout() {
return (
- <Tabs>
- <Tabs.Screen name="index" options={{ title: "Home" }} />
- <Tabs.Screen name="profile" options={{ title: "Profile" }} />
+ <Tabs screenOptions={{ tabBarActiveTintColor: theme.colorGreen }}>
+ <Tabs.Screen
+ name="index"
+ options={{
+ title: "Home",
+ tabBarShowLabel: false,
+ tabBarIcon: ({ size, color }) => (
+ <Entypo name="leaf" size={size} color={color} />
+ ),
+ }}
+ />
+ <Tabs.Screen
+ name="profile"
+ options={{
+ title: "Profile",
+ tabBarShowLabel: false,
+ tabBarIcon: ({ size, color }) => (
+ <Feather name="user" size={size} color={color} />
+ ),
+ }}
+ />
</Tabs>
);
}

Add a theme file (optional)​

When using StyleSheet for styling, it's a good practice to define a theme file to avoid duplicating primitives like colors.

New file: theme.ts
export const theme = {
colorGreen: "#29b365",
colorWhite: "#fff",
};
Update: app/_layout.tsx
@@ -1,10 +1,11 @@
import { Tabs } from "expo-router";
import Entypo from "@expo/vector-icons/Entypo";
import Feather from "@expo/vector-icons/Feather";
+import { theme } from "../theme";

export default function Layout() {
return (
- <Tabs screenOptions={{ tabBarActiveTintColor: "#29b365" }}>
+ <Tabs screenOptions={{ tabBarActiveTintColor: theme.colorGreen }}>
<Tabs.Screen
name="index"
options={{
Update: app/profile.tsx
@@ -1,4 +1,5 @@
import { Text, View, StyleSheet } from "react-native";
+import { theme } from "../theme";

export default function ProfileScreen() {
return (
@@ -13,7 +14,7 @@ const styles = StyleSheet.create({
flex: 1,
justifyContent: "center",
alignItems: "center",
- backgroundColor: "#fff",
+ backgroundColor: theme.colorWhite,
},
text: {
fontSize: 24,
Update: app/index.tsx
@@ -1,5 +1,6 @@
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View } from "react-native";
+import { theme } from "../theme";

export default function App() {
return (
@@ -13,7 +14,7 @@ export default function App() {
const styles = StyleSheet.create({
container: {
flex: 1,
- backgroundColor: "#fff",
+ backgroundColor: theme.colorWhite,
alignItems: "center",
justifyContent: "center",
},

Hide the tab bar text​

In this case, I think the tab bar icons are enough to communicate what each screen is about so let's hide the tab bar label and keep it icon-only.

Update: app/_layout.tsx
@@ -10,6 +10,7 @@ export default function Layout() {
name="index"
options={{
title: "Home",
+ tabBarShowLabel: false,
tabBarIcon: ({ color, size }) => (
<Entypo name="leaf" size={size} color={color} />
),
@@ -19,6 +20,7 @@ export default function Layout() {
name="profile"
options={{
title: "Profile",
+ tabBarShowLabel: false,
tabBarIcon: ({ color, size }) => (
<Feather name="user" size={size} color={color} />
),

Checkpoint​

AndroidiOS
Checkpoint