Tabs
Tabs are a bar at the bottom of the screen that switches between the main sections of an app. The blog gets two tabs: "Posts" and "Profile". The post screen should still open on top of the tabs, with a back arrow.
File structure
Tab screens go into a group folder. A name in parentheses, such as (tabs), groups files but is not part of the address: app/(tabs)/profile.tsx opens at /profile.
app/
├── _layout.tsx root stack
├── (tabs)/
│ ├── _layout.tsx tab bar
│ ├── index.tsx "Posts" tab, address /
│ └── profile.tsx "Profile" tab, address /profile
└── posts/
└── [id].tsx post screen on top of the tabsMove app/index.tsx to app/(tabs)/index.tsx and create the profile screen:
// app/(tabs)/profile.tsx
import { Text, View } from 'react-native';
export default function Profile() {
return (
<View style={{ flex: 1, padding: 16 }}>
<Text style={{ fontSize: 18 }}>Anna, reader since 2024</Text>
</View>
);
}The tabs layout
Create a layout inside the group:
// app/(tabs)/_layout.tsx
import { Ionicons } from '@expo/vector-icons';
import { Tabs } from 'expo-router';
export default function TabsLayout() {
return (
<Tabs screenOptions={{ tabBarActiveTintColor: '#2563eb' }}>
<Tabs.Screen
name="index"
options={{
title: 'Posts',
tabBarIcon: ({ color, size }) => <Ionicons name="newspaper" color={color} size={size} />,
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color, size }) => <Ionicons name="person" color={color} size={size} />,
}}
/>
</Tabs>
);
}Two tabs with newspaper and person icons appear at the bottom; the active one is highlighted in blue. tabBarIcon receives the color and size matching the tab's current state. The @expo/vector-icons package is already in the template.
The root stack
The tab group becomes one screen of the root stack. The tabs have their own header, so the stack header is hidden for them:
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="posts/[id]" options={{ title: 'Post' }} />
</Stack>
);
}Tap a post in the "Posts" tab: its screen opens on top, the tab bar is hidden, and the back arrow returns to the list. The links in app/(tabs)/index.tsx do not need to change, because the /posts/[id] address stays the same.
Common mistake
Keeping both app/index.tsx and app/(tabs)/index.tsx. Both files claim the / address, and Expo Router cannot choose a screen. The main screen must exist in exactly one place, so delete the old file after moving it.