Skip to main content
In this tutorial, you’ll learn how to add multiple screens to your app and navigate between them using Expo Router. You’ll create a multi-screen app with tab navigation and understand how file-based routing works.

What You’ll Build

A multi-screen app with:
  • Home screen
  • Profile screen
  • Settings screen
  • Tab navigation between screens
  • Dynamic routes for user profiles
Time required: 15-20 minutes

Prerequisites

Complete the Create Your First Project tutorial first.

Understanding File-Based Routing

Expo Router uses your file structure to create routes automatically. Files in the app/ directory become screens:
app/
  index.tsx           # Route: /
  about.tsx           # Route: /about
  profile/
    index.tsx         # Route: /profile
    [id].tsx          # Route: /profile/:id
  (tabs)/             # Group (not in URL)
    home.tsx          # Route: /home
    settings.tsx      # Route: /settings
No route configuration needed - just create files and they become routes!

Step 1: Create Additional Screens

Let’s add two new screens to your app.

Create Profile Screen

Create app/profile.tsx:
app/profile.tsx
import { StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Link } from 'expo-router';

import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';

export default function ProfileScreen() {
  return (
    <ThemedView style={styles.container}>
      <SafeAreaView style={styles.safeArea}>
        <ThemedView style={styles.content}>
          <ThemedText type="title">Profile Screen</ThemedText>
          
          <ThemedText style={styles.info}>
            This is your profile page.
          </ThemedText>

          <Link href="/" style={styles.link}>
            <ThemedText type="link">Go back home</ThemedText>
          </Link>
        </ThemedView>
      </SafeAreaView>
    </ThemedView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  safeArea: {
    flex: 1,
  },
  content: {
    flex: 1,
    padding: 16,
    gap: 16,
    justifyContent: 'center',
    alignItems: 'center',
  },
  info: {
    textAlign: 'center',
  },
  link: {
    marginTop: 16,
  },
});

Create Settings Screen

Create app/settings.tsx:
app/settings.tsx
import { StyleSheet, Switch } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Link } from 'expo-router';
import { useState } from 'react';

import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';

export default function SettingsScreen() {
  const [notifications, setNotifications] = useState(true);
  const [darkMode, setDarkMode] = useState(false);

  return (
    <ThemedView style={styles.container}>
      <SafeAreaView style={styles.safeArea}>
        <ThemedView style={styles.content}>
          <ThemedText type="title">Settings</ThemedText>

          <ThemedView style={styles.settingRow}>
            <ThemedText>Enable Notifications</ThemedText>
            <Switch
              value={notifications}
              onValueChange={setNotifications}
            />
          </ThemedView>

          <ThemedView style={styles.settingRow}>
            <ThemedText>Dark Mode</ThemedText>
            <Switch
              value={darkMode}
              onValueChange={setDarkMode}
            />
          </ThemedView>

          <Link href="/" style={styles.link}>
            <ThemedText type="link">Go back home</ThemedText>
          </Link>
        </ThemedView>
      </SafeAreaView>
    </ThemedView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  safeArea: {
    flex: 1,
  },
  content: {
    flex: 1,
    padding: 16,
    gap: 16,
  },
  settingRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 16,
    borderRadius: 8,
    backgroundColor: 'rgba(128, 128, 128, 0.1)',
  },
  link: {
    marginTop: 16,
    alignSelf: 'center',
  },
});
Save both files and keep your dev server running. Update your home screen to link to the new screens:
app/index.tsx
import { StyleSheet, Pressable } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Link } from 'expo-router';

import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';

export default function HomeScreen() {
  return (
    <ThemedView style={styles.container}>
      <SafeAreaView style={styles.safeArea}>
        <ThemedView style={styles.content}>
          <ThemedText type="title" style={styles.title}>
            Welcome to Expo Router
          </ThemedText>

          <ThemedText style={styles.subtitle}>
            Navigate between screens:
          </ThemedText>

          <ThemedView style={styles.navigation}>
            <Link href="/profile" asChild>
              <Pressable style={styles.button}>
                <ThemedText type="subtitle" style={styles.buttonText}>
                  View Profile
                </ThemedText>
              </Pressable>
            </Link>

            <Link href="/settings" asChild>
              <Pressable style={styles.button}>
                <ThemedText type="subtitle" style={styles.buttonText}>
                  Open Settings
                </ThemedText>
              </Pressable>
            </Link>
          </ThemedView>
        </ThemedView>
      </SafeAreaView>
    </ThemedView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  safeArea: {
    flex: 1,
  },
  content: {
    flex: 1,
    padding: 16,
    gap: 24,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    textAlign: 'center',
  },
  subtitle: {
    textAlign: 'center',
    opacity: 0.7,
  },
  navigation: {
    gap: 16,
    width: '100%',
    maxWidth: 300,
  },
  button: {
    padding: 16,
    borderRadius: 8,
    backgroundColor: '#007AFF',
    alignItems: 'center',
  },
  buttonText: {
    color: '#FFFFFF',
  },
});
Save and test! Tap the buttons to navigate between screens.

Step 3: Add Tab Navigation

Let’s create a tab bar at the bottom of the app.

Create Tabs Group

Create a new folder and layout:
mkdir -p app/\(tabs\)
Create app/(tabs)/_layout.tsx:
app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Platform } from 'react-native';

export default function TabLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#007AFF',
        tabBarStyle: {
          backgroundColor: Platform.OS === 'ios' ? 'transparent' : '#FFFFFF',
        },
        headerShown: false,
      }}
    >
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color }) => <HomeIcon color={color} />,
        }}
      />
      <Tabs.Screen
        name="explore"
        options={{
          title: 'Explore',
          tabBarIcon: ({ color }) => <ExploreIcon color={color} />,
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color }) => <ProfileIcon color={color} />,
        }}
      />
    </Tabs>
  );
}

// Simple icon components
function HomeIcon({ color }: { color: string }) {
  return (
    <svg width="24" height="24" viewBox="0 0 24 24" fill={color}>
      <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
    </svg>
  );
}

function ExploreIcon({ color }: { color: string }) {
  return (
    <svg width="24" height="24" viewBox="0 0 24 24" fill={color}>
      <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
    </svg>
  );
}

function ProfileIcon({ color }: { color: string }) {
  return (
    <svg width="24" height="24" viewBox="0 0 24 24" fill={color}>
      <path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" />
    </svg>
  );
}
The (tabs) folder name uses parentheses to create a route group that doesn’t affect the URL path.

Move Screens to Tabs

Move your screens into the tabs group:
mv app/index.tsx app/\(tabs\)/index.tsx
mv app/explore.tsx app/\(tabs\)/explore.tsx
mv app/profile.tsx app/\(tabs\)/profile.tsx
Update your root layout app/_layout.tsx:
app/_layout.tsx
import { Stack } from 'expo-router';
import { ThemeProvider, DarkTheme, DefaultTheme } from '@react-navigation/native';
import { useColorScheme } from 'react-native';

export default function RootLayout() {
  const colorScheme = useColorScheme();

  return (
    <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
      <Stack
        screenOptions={{
          headerShown: false,
        }}
      >
        <Stack.Screen name="(tabs)" />
        <Stack.Screen name="settings" />
      </Stack>
    </ThemeProvider>
  );
}
Now you have a tab bar at the bottom! Try switching between tabs.

Step 4: Add Dynamic Routes

Dynamic routes let you create pages based on parameters (like user IDs). Create app/(tabs)/profile/[id].tsx:
mkdir app/\(tabs\)/profile
mv app/\(tabs\)/profile.tsx app/\(tabs\)/profile/index.tsx
Create app/(tabs)/profile/[id].tsx:
app/(tabs)/profile/[id].tsx
import { StyleSheet } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useLocalSearchParams, router } from 'expo-router';

import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';
import { Pressable } from 'react-native';

export default function UserProfileScreen() {
  const { id } = useLocalSearchParams<{ id: string }>();

  return (
    <ThemedView style={styles.container}>
      <SafeAreaView style={styles.safeArea}>
        <ThemedView style={styles.content}>
          <ThemedText type="title">User Profile</ThemedText>
          
          <ThemedText style={styles.userId}>
            Viewing profile for user: {id}
          </ThemedText>

          <ThemedText style={styles.info}>
            This is a dynamic route. The ID comes from the URL!
          </ThemedText>

          <Pressable
            style={styles.button}
            onPress={() => router.back()}
          >
            <ThemedText style={styles.buttonText}>Go Back</ThemedText>
          </Pressable>
        </ThemedView>
      </SafeAreaView>
    </ThemedView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  safeArea: {
    flex: 1,
  },
  content: {
    flex: 1,
    padding: 16,
    gap: 16,
    justifyContent: 'center',
    alignItems: 'center',
  },
  userId: {
    fontSize: 18,
    fontWeight: 'bold',
  },
  info: {
    textAlign: 'center',
    opacity: 0.7,
  },
  button: {
    marginTop: 16,
    padding: 16,
    borderRadius: 8,
    backgroundColor: '#007AFF',
  },
  buttonText: {
    color: '#FFFFFF',
  },
});
Update app/(tabs)/profile/index.tsx to link to dynamic routes:
app/(tabs)/profile/index.tsx
import { StyleSheet, Pressable, ScrollView } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Link } from 'expo-router';

import { ThemedText } from '@/components/themed-text';
import { ThemedView } from '@/components/themed-view';

const USERS = [
  { id: '1', name: 'Alice Johnson' },
  { id: '2', name: 'Bob Smith' },
  { id: '3', name: 'Carol Williams' },
];

export default function ProfileScreen() {
  return (
    <ThemedView style={styles.container}>
      <SafeAreaView style={styles.safeArea}>
        <ScrollView contentContainerStyle={styles.content}>
          <ThemedText type="title">Profile</ThemedText>
          
          <ThemedText style={styles.subtitle}>
            Select a user to view their profile:
          </ThemedText>

          <ThemedView style={styles.userList}>
            {USERS.map((user) => (
              <Link
                key={user.id}
                href={`/profile/${user.id}`}
                asChild
              >
                <Pressable style={styles.userCard}>
                  <ThemedText type="subtitle">{user.name}</ThemedText>
                  <ThemedText style={styles.arrow}></ThemedText>
                </Pressable>
              </Link>
            ))}
          </ThemedView>
        </ScrollView>
      </SafeAreaView>
    </ThemedView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  safeArea: {
    flex: 1,
  },
  content: {
    padding: 16,
    gap: 16,
  },
  subtitle: {
    opacity: 0.7,
  },
  userList: {
    gap: 12,
  },
  userCard: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 16,
    borderRadius: 8,
    backgroundColor: 'rgba(128, 128, 128, 0.1)',
  },
  arrow: {
    opacity: 0.5,
  },
});
Tap on a user to navigate to their dynamic profile page!
Expo Router provides multiple ways to navigate:
import { Link } from 'expo-router';

<Link href="/profile">Go to Profile</Link>

// With params
<Link href="/profile/123">User 123</Link>

// With query params
<Link href={{ pathname: '/profile', params: { id: '123', tab: 'posts' } }}>
  Profile
</Link>

Using router Object

import { router } from 'expo-router';

// Push (add to stack)
router.push('/profile');

// Replace (replace current)
router.replace('/login');

// Go back
router.back();

// Navigate with params
router.push({
  pathname: '/profile/[id]',
  params: { id: '123' }
});

Programmatic Navigation

import { router } from 'expo-router';
import { Pressable } from 'react-native';

function NavigateButton() {
  const handlePress = () => {
    // Do something first
    console.log('Navigating...');
    
    // Then navigate
    router.push('/profile');
  };

  return (
    <Pressable onPress={handlePress}>
      <Text>Go to Profile</Text>
    </Pressable>
  );
}

Advanced Routing Patterns

Create a modal by adding it to the root stack:
app/_layout.tsx
<Stack>
  <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
  <Stack.Screen
    name="modal"
    options={{
      presentation: 'modal',
      title: 'Modal Screen',
    }}
  />
</Stack>

Nested Navigators

Create complex hierarchies:
app/
  (tabs)/
    _layout.tsx       # Tab navigator
    index.tsx
    profile/
      _layout.tsx     # Stack navigator for profile
      index.tsx
      [id].tsx
      edit.tsx

Catch-All Routes

Handle 404s with +not-found.tsx:
app/+not-found.tsx
import { Link } from 'expo-router';
import { ThemedView } from '@/components/themed-view';
import { ThemedText } from '@/components/themed-text';

export default function NotFoundScreen() {
  return (
    <ThemedView style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <ThemedText type="title">Page Not Found</ThemedText>
      <Link href="/">
        <ThemedText type="link">Go home</ThemedText>
      </Link>
    </ThemedView>
  );
}

Type-Safe Routes

Expo Router generates types for all your routes:
import { Href } from 'expo-router';

// Type-safe route
const profileRoute: Href = '/profile/123';

// TypeScript error for invalid route
const invalidRoute: Href = '/nonexistent'; // Error!
File names become routes, so make them clear:
# Good
app/user-profile.tsx          # /user-profile
app/settings/notifications.tsx # /settings/notifications

# Avoid
app/up.tsx                     # /up (unclear)
app/page1.tsx                  # /page1 (generic)
Always handle back navigation:
import { router } from 'expo-router';

function BackButton() {
  return (
    <Pressable onPress={() => router.back()}>
      <Text>← Back</Text>
    </Pressable>
  );
}

Troubleshooting

Check:
  1. File is in app/ directory
  2. File exports a default component
  3. Dev server restarted after adding file
Use useLocalSearchParams correctly:
import { useLocalSearchParams } from 'expo-router';

function Screen() {
  const { id } = useLocalSearchParams<{ id: string }>();
  
  if (!id) {
    return <Text>Loading...</Text>;
  }
  
  return <Text>User: {id}</Text>;
}

Next Steps

Congratulations! You’ve learned:
  • File-based routing basics
  • Creating multiple screens
  • Tab navigation
  • Dynamic routes
  • Navigation methods
  • Best practices

Build & Deploy

Ship your app to production

Router Documentation

Advanced routing features

SDK Modules

Add more features to your app

Core Concepts

Understand Expo architecture