Navigation is the backbone of any mobile app. Users expect to move between screens, go back, switch tabs, and access side menus — all with smooth animations and predictable behaviour. React Navigation is the de facto standard library for handling all of this in React Native. This guide walks you through the three core navigator types with complete, working TypeScript code.
Installation and Setup
First, install React Navigation and its dependencies. The library is modular, so you install only what you need:
# Core library
npm install @react-navigation/native
# Required peer dependencies
npm install react-native-screens react-native-safe-area-context
# Navigator packages (install the ones you need)
npm install @react-navigation/native-stack
npm install @react-navigation/bottom-tabs
npm install @react-navigation/drawer
# Drawer requires gesture handler and reanimated
npm install react-native-gesture-handler react-native-reanimated
# iOS pods
cd ios && pod install && cd ..
Wrap your app with NavigationContainer — this is the top-level component that manages the navigation tree:
// App.tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { RootNavigator } from './navigation/RootNavigator';
export default function App(): React.JSX.Element {
return (
<NavigationContainer>
<RootNavigator />
</NavigationContainer>
);
}
Stack Navigator: Screen-to-Screen Navigation
Stack navigation mimics a deck of cards — each new screen slides on top of the previous one. Define your type-safe route parameters first, then create the navigator:
// navigation/types.ts
export type RootStackParamList = {
Home: undefined;
Details: { itemId: number; title: string };
Profile: { userId: string };
};
// screens/HomeScreen.tsx
import React from 'react';
import { View, Text, FlatList, TouchableOpacity, StyleSheet } from 'react-native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../navigation/types';
type Props = NativeStackScreenProps<RootStackParamList, 'Home'>;
const ITEMS = Array.from({ length: 20 }, (_, i) => ({
id: i + 1,
title: `Item ${i + 1}`,
}));
export function HomeScreen({ navigation }: Props): React.JSX.Element {
return (
<FlatList
data={ITEMS}
keyExtractor={(item) => item.id.toString()}
contentContainerStyle={styles.list}
renderItem={({ item }) => (
<TouchableOpacity
style={styles.card}
onPress={() =>
navigation.navigate('Details', {
itemId: item.id,
title: item.title,
})
}
>
<Text style={styles.cardTitle}>{item.title}</Text>
<Text style={styles.cardArrow}>→</Text>
</TouchableOpacity>
)}
/>
);
}
const styles = StyleSheet.create({
list: { padding: 16 },
card: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
marginBottom: 8,
backgroundColor: '#fff',
borderRadius: 8,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 2,
},
cardTitle: { fontSize: 16, fontWeight: '600' },
cardArrow: { fontSize: 18, color: '#6366F1' },
});
// screens/DetailsScreen.tsx
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { RootStackParamList } from '../navigation/types';
type Props = NativeStackScreenProps<RootStackParamList, 'Details'>;
export function DetailsScreen({ route, navigation }: Props): React.JSX.Element {
const { itemId, title } = route.params;
React.useEffect(() => {
navigation.setOptions({ title });
}, [navigation, title]);
return (
<View style={styles.container}>
<Text style={styles.heading}>{title}</Text>
<Text style={styles.detail}>Item ID: {itemId}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
heading: { fontSize: 28, fontWeight: '700', marginBottom: 8 },
detail: { fontSize: 16, color: '#666' },
});
Now wire the stack navigator:
// navigation/StackNavigator.tsx
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { RootStackParamList } from './types';
import { HomeScreen } from '../screens/HomeScreen';
import { DetailsScreen } from '../screens/DetailsScreen';
const Stack = createNativeStackNavigator<RootStackParamList>();
export function StackNavigator(): React.JSX.Element {
return (
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: { backgroundColor: '#4F46E5' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: '700' },
}}
>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
);
}
Tab Navigator: Bottom Navigation
Tabs let users switch between top-level sections. This is the most common navigation pattern in mobile apps:
// navigation/TabNavigator.tsx
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { StackNavigator } from './StackNavigator';
import { ProfileScreen } from '../screens/ProfileScreen';
import { SettingsScreen } from '../screens/SettingsScreen';
type TabParamList = {
HomeTab: undefined;
ProfileTab: undefined;
SettingsTab: undefined;
};
const Tab = createBottomTabNavigator<TabParamList>();
export function TabNavigator(): React.JSX.Element {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
headerShown: false,
tabBarActiveTintColor: '#4F46E5',
tabBarInactiveTintColor: '#9CA3AF',
tabBarIcon: ({ color, size }) => {
const icons: Record<string, string> = {
HomeTab: '\u2302',
ProfileTab: '\u263A',
SettingsTab: '\u2699',
};
return (
<Text style={{ fontSize: size, color }}>
{icons[route.name] || '?'}
</Text>
);
},
})}
>
<Tab.Screen
name="HomeTab"
component={StackNavigator}
options={{ tabBarLabel: 'Home' }}
/>
<Tab.Screen
name="ProfileTab"
component={ProfileScreen}
options={{ tabBarLabel: 'Profile' }}
/>
<Tab.Screen
name="SettingsTab"
component={SettingsScreen}
options={{ tabBarLabel: 'Settings' }}
/>
</Tab.Navigator>
);
}
Notice that HomeTab nests the entire StackNavigator inside a tab. This is a standard pattern — each tab can have its own navigation stack, so pushing a details screen does not affect other tabs.
Drawer Navigator: Side Menu
Drawers provide a slide-out menu, typically accessed via a hamburger icon or swipe gesture:
// navigation/DrawerNavigator.tsx
import React from 'react';
import {
createDrawerNavigator,
DrawerContentScrollView,
DrawerItemList,
DrawerItem,
} from '@react-navigation/drawer';
import { View, Text, StyleSheet } from 'react-native';
import { TabNavigator } from './TabNavigator';
import { AboutScreen } from '../screens/AboutScreen';
type DrawerParamList = {
Main: undefined;
About: undefined;
};
const Drawer = createDrawerNavigator<DrawerParamList>();
function CustomDrawerContent(props: any): React.JSX.Element {
return (
<DrawerContentScrollView {...props}>
<View style={styles.header}>
<Text style={styles.headerText}>MyApp</Text>
<Text style={styles.headerSub}>v1.0.0</Text>
</View>
<DrawerItemList {...props} />
<DrawerItem
label="Logout"
onPress={() => console.log('Logout pressed')}
/>
</DrawerContentScrollView>
);
}
export function DrawerNavigator(): React.JSX.Element {
return (
<Drawer.Navigator
drawerContent={(props) => <CustomDrawerContent {...props} />}
screenOptions={{
drawerActiveTintColor: '#4F46E5',
headerStyle: { backgroundColor: '#4F46E5' },
headerTintColor: '#fff',
}}
>
<Drawer.Screen name="Main" component={TabNavigator} />
<Drawer.Screen name="About" component={AboutScreen} />
</Drawer.Navigator>
);
}
const styles = StyleSheet.create({
header: { padding: 20, borderBottomWidth: 1, borderBottomColor: '#E5E7EB' },
headerText: { fontSize: 20, fontWeight: '700' },
headerSub: { fontSize: 14, color: '#6B7280', marginTop: 4 },
});
Combining All Three: The Root Navigator
In a production app, you often nest navigators: a drawer wraps tabs, and each tab wraps a stack. The RootNavigator ties it all together:
// navigation/RootNavigator.tsx
import React from 'react';
import { DrawerNavigator } from './DrawerNavigator';
export function RootNavigator(): React.JSX.Element {
// In a real app you might check auth state here
// and conditionally render an auth stack
return <DrawerNavigator />;
}
The nesting hierarchy is: Drawer → Tabs → Stack. Each level manages its own navigation state independently.
Conclusion
React Navigation gives you the building blocks to create any navigation structure your app needs. Start simple with a stack navigator, add bottom tabs as your app grows, and introduce a drawer when you have enough top-level sections to justify it. The TypeScript type definitions ensure you never pass the wrong parameters to a screen, catching bugs at compile time instead of runtime. For advanced patterns like deep linking, authentication flows, and shared element transitions, the official React Navigation documentation at reactnavigation.org is comprehensive and well-maintained.