Skip to main content

Bottom tab navigation

The bottom tabs navigation means rendering the screens (or stacks of screens!) as bottom tabs where the tabs are always visible so you can easily switch between them.

Use a tab navigator

With Expo Router, it's actually delightfully easy to set this up: simply change the Stack in your layout file to Tabs.

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

export default function Layout() {
return (
- <Stack>
- <Stack.Screen name="index" options={{ title: "Shopping list" }} />
- <Stack.Screen
- name="counter"
- options={{ title: "Counter", presentation: "modal" }}
- />
- <Stack.Screen
- name="idea"
- options={{ title: "My idea", presentation: "modal" }}
- />
- </Stack>
+ <Tabs>
+ <Tabs.Screen name="index" options={{ title: "Shopping list" }} />
+ <Tabs.Screen name="counter" options={{ title: "Counter" }} />
+ <Tabs.Screen name="idea" options={{ title: "My idea" }} />
+ </Tabs>
);
}

Verify that you can still navigate between the screens using Link and useRouter, but now that we have bottom tabs, we can use those to navigate and we don't really need this so let's also remove that code.

Update: app/counter.tsx
@@ -1,16 +1,8 @@
-import { useRouter } from "expo-router";
-import { Text, View, StyleSheet, TouchableOpacity } from "react-native";
+import { Text, View, StyleSheet } from "react-native";

export default function CounterScreen() {
- const router = useRouter();
-
return (
<View style={styles.container}>
- <TouchableOpacity onPress={() => router.navigate("/idea")}>
- <Text style={{ textAlign: "center", marginBottom: 18, fontSize: 24 }}>
- Go to /idea
- </Text>
- </TouchableOpacity>
<Text style={styles.text}>Counter</Text>
</View>
);
Update: app/index.tsx
@@ -1,17 +1,10 @@
import { StyleSheet, View } from "react-native";
import { theme } from "../theme";
import { ShoppingListItem } from "../components/ShoppingListItem";
-import { Link } from "expo-router";

export default function App() {
return (
<View style={styles.container}>
- <Link
- href="/counter"
- style={{ textAlign: "center", marginBottom: 18, fontSize: 24 }}
- >
- Go to /counter
- </Link>
<ShoppingListItem name="Coffee" />
<ShoppingListItem name="Tea" isCompleted />
<ShoppingListItem name="Milk" isCompleted />

Choose icons for the bottom tabs

By default we get some placeholder icons on the bottom tabs. Let's choose something more appropriate from @expo/vector-icons.

Update: app/_layout.tsx
@@ -1,11 +1,39 @@
import { Tabs } from "expo-router";
+import Feather from "@expo/vector-icons/Feather";
+import FontAwesome5 from "@expo/vector-icons/FontAwesome5";
+import AntDesign from "@expo/vector-icons/AntDesign";
+import { theme } from "../theme";

export default function Layout() {
return (
- <Tabs>
- <Tabs.Screen name="index" options={{ title: "Shopping list" }} />
- <Tabs.Screen name="counter" options={{ title: "Counter" }} />
- <Tabs.Screen name="idea" options={{ title: "My idea" }} />
+ <Tabs screenOptions={{ tabBarActiveTintColor: theme.colorCerulean }}>
+ <Tabs.Screen
+ name="index"
+ options={{
+ title: "Shopping list",
+ tabBarIcon: ({ color, size }) => (
+ <Feather name="list" size={size} color={color} />
+ ),
+ }}
+ />
+ <Tabs.Screen
+ name="counter"
+ options={{
+ title: "Counter",
+ tabBarIcon: ({ color, size }) => (
+ <AntDesign name="clockcircleo" size={size} color={color} />
+ ),
+ }}
+ />
+ <Tabs.Screen
+ name="idea"
+ options={{
+ title: "My idea",
+ tabBarIcon: ({ color, size }) => (
+ <FontAwesome5 name="lightbulb" size={size} color={color} />
+ ),
+ }}
+ />
</Tabs>
);
}
Plugin Error

Make sure you use the color and size props passed into tabBarIcon to have Expo Router handle the active and inactive colors for the tabs. You can configure the tint color globally by setting the tabBarActiveTintColor on the screenOptions of the containing Tabs.

Checkpoint

AndroidiOS