Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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 tabs

Move 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.

Thanks for reading!

Donate to the site — we work hard for you.