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
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:
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:
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.
Step 2: Add Navigation Links
Update your home screen to link to the new screens:
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:
Create 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:
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!
Navigation Methods
Expo Router provides multiple ways to navigate:
Using Link Component
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
Modal Routes
Create a modal by adding it to the root stack:
< 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:
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!
Navigation Best Practices
Use descriptive file names
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 >
);
}
Design URLs that work as deep links: # Good
/product/123
/user/profile/settings
/cart/checkout
# Avoid
/p/123 # Not descriptive
/screen2 # Generic
Troubleshooting
Check:
File is in app/ directory
File exports a default component
Dev server restarted after adding file
Verify:
Using correct href format
Route exists in file structure
No typos in route path
// Correct
< Link href = "/profile" > Profile </ Link >
// Wrong
< Link href = "profile" > Profile </ Link > // Missing /
Dynamic route params undefined
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