Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/expo/expo/llms.txt

Use this file to discover all available pages before exploring further.

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

Build docs developers (and LLMs) love