React Native Drawer Navigation with Expo Router

Last update: 2025-05-20
Type
Quick Win's logo Quick Win
Membership
🆓
Tech
React Native React Native
React Native
TypeScript TypeScript
TypeScript
Share:

Welcome! In this quick win, we’ll learn how to add a beautiful, flexible drawer navigation to your React Native app using Expo Router and React Navigation’s Drawer. We’ll cover the basics, add custom styling, and even create dynamic drawer items. Let’s get started!

Galaxies-dev/
expo-router-drawer-essentials

React Native Drawer Navigation with Expo Router
Galaxies Github Icon
Get this code Want to play around with this code locally? Download the code and see it all in context. Galaxies members get all the code & so much more. I can't access the code!

Why Drawer Navigation?

Drawer navigation is a classic pattern for mobile apps, letting users easily switch between major sections. With Expo Router, integrating a drawer is easier than ever—and you get all the power of React Navigation under the hood.


1. Setting Up Your Project

Let’s start by creating a new Expo app and installing the required dependencies.

Command
npx create-expo-app my-drawer
cd my-drawer
# remove the existing app
npm run reset-project
# install the dependencies
npx expo install @react-navigation/drawer react-native-gesture-handler react-native-reanimated

These packages give us everything we need for drawer navigation and smooth gestures.


2. Basic Drawer Layout

Let’s create a simple drawer layout. This is the foundation for your navigation structure.

app/_layout.tsx
import { Drawer } from 'expo-router/drawer';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

export default function Layout() {
	return (
		<GestureHandlerRootView style={{ flex: 1 }}>
			<Drawer />
		</GestureHandlerRootView>
	);
}

What’s happening here?

  • We wrap everything in GestureHandlerRootView for gesture support.
  • The <Drawer /> component from Expo Router automatically picks up your routes and displays them in a drawer.

You can run your app, and should see a drawer on the left side of the screen.

3. Customizing Drawer Navigation

Let’s add some style and custom screens to our drawer. You can control the look and feel, icons, and more!

You can simply create a new file in the app folder and add a new screen - I’ve called mine news.tsx.

app/_layout.tsx
import { Drawer } from 'expo-router/drawer';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import Ionicons from '@expo/vector-icons/Ionicons';

export default function Layout() {
	return (
		<GestureHandlerRootView style={{ flex: 1 }}>
			<Drawer
				screenOptions={{
					drawerActiveTintColor: 'red',
					drawerHideStatusBarOnOpen: true
				}}
			>
				<Drawer.Screen
					name="index"
					options={{
						drawerLabel: 'Home',
						title: 'My App',
						drawerIcon: ({ color, size }) => <Ionicons name="home" size={size} color={color} />
					}}
				/>
				<Drawer.Screen
					name="news"
					options={{
						drawerLabel: 'News',
						title: 'News',
						drawerIcon: ({ color, size }) => <Ionicons name="newspaper" size={size} color={color} />
					}}
				/>
			</Drawer>
		</GestureHandlerRootView>
	);
}

What’s new?

  • We use screenOptions to style the drawer (active color, status bar behavior).
  • Each <Drawer.Screen /> defines a route, label, title, and icon.
  • The name of the screen is the name of the file!

4. Dynamic Drawer Items & Custom Drawer Content

Want to add dynamic items or a custom drawer UI? Here’s how you can do it!

Imagine you fetch a list of items from an API and want to display them in the drawer, just like ChatGPT is doing for past chats.

app/_layout.tsx
import Ionicons from '@expo/vector-icons/Ionicons';
import {
	DrawerContentComponentProps,
	DrawerContentScrollView,
	DrawerItem,
	DrawerItemList
} from '@react-navigation/drawer';
import { usePathname, useRouter } from 'expo-router';
import { Drawer } from 'expo-router/drawer';
import { Image, Text, View } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';

function CustomDrawerContent(props: DrawerContentComponentProps) {
	const menuItems = [
		{ id: 42, title: 'Item 42' },
		{ id: 43, title: 'Item 43' },
		{ id: 44, title: 'Item 44' }
	];
	const router = useRouter();
	const pathname = usePathname();

	return (
		<DrawerContentScrollView {...props}>
			<View style={{ padding: 16, alignItems: 'center' }}>
				<Image
					source={require('@/assets/images/react-logo.png')}
					style={{ width: 100, height: 100, borderRadius: 50 }}
				/>
			</View>
			<DrawerItemList {...props} />

			<View style={{ padding: 16, paddingTop: 40 }}>
				<Text style={{ fontSize: 16, fontWeight: 'bold' }}>Items</Text>
			</View>

			{menuItems.map((item) => {
				const isActive = pathname === `/${item.id}`;

				return (
					<DrawerItem
						activeTintColor="red"
						focused={isActive}
						key={item.id}
						label={item.title}
						onPress={() => router.push(`/${item.id}`)}
					/>
				);
			})}
		</DrawerContentScrollView>
	);
}

export default function RootLayout() {
	return (
		<GestureHandlerRootView style={{ flex: 1 }}>
			<Drawer
				drawerContent={CustomDrawerContent}
				screenOptions={{
					drawerActiveTintColor: 'red',
					drawerHideStatusBarOnOpen: true
				}}
			>
				<Drawer.Screen
					name="index"
					options={{
						drawerLabel: 'Home',
						title: 'My App',
						drawerIcon: ({ color, size }) => <Ionicons name="home" size={size} color={color} />
					}}
				/>
				<Drawer.Screen
					name="news"
					options={{
						drawerLabel: 'News',
						title: 'News',
						drawerIcon: ({ color, size }) => <Ionicons name="newspaper" size={size} color={color} />
					}}
				/>
				<Drawer.Screen
					name="[id]"
					options={{
						drawerItemStyle: {
							display: 'none'
						}
					}}
				/>
			</Drawer>
		</GestureHandlerRootView>
	);
}

What’s going on?

  • We create a CustomDrawerContent component for a personalized drawer UI.
  • Add a logo, default items, and a dynamic list of extra items.
  • Use router.push to navigate to dynamic routes.
  • Hide the [id] route from the drawer but keep it accessible - otherwise it would be shown in the drawer as well!
  • We check if the current route is active and set the active color accordingly in combination with focused={isActive}.

5. Dynamic Page Example

Let’s see how a dynamic page works with our drawer setup.

Simply create a new file in the app folder and add a new screen - I’ve called mine [id].tsx.

app/[id].tsx
import { Stack, useLocalSearchParams } from 'expo-router';
import { Text, View } from 'react-native';

const Page = () => {
	const { id } = useLocalSearchParams();
	return (
		<View
			style={{
				flex: 1,
				justifyContent: 'center',
				alignItems: 'center'
			}}
		>
			<Stack.Screen options={{ title: `Page ${id}` }} />
			<Text>You are on Page {id}</Text>
		</View>
	);
};
export default Page;

How does this work?

  • The page grabs the id from the route and displays it.
  • The drawer can now link to any dynamic page you want!

6. Drawer Utility Functions

Want to toggle the drawer from a button or header? Here’s how:

app/index.tsx
import { DrawerToggleButton } from '@react-navigation/drawer';
import { DrawerActions } from '@react-navigation/native';
import { Stack, useNavigation } from 'expo-router';
import { Button, View } from 'react-native';

export default function Index() {
	const navigation = useNavigation();

	return (
		<View
			style={{
				flex: 1,
				justifyContent: 'center',
				alignItems: 'center'
			}}
		>
			<Stack.Screen
				options={{
					headerRight: () => <DrawerToggleButton />
				}}
			/>

			<Button
				title="Toggle Drawer"
				onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
			/>
		</View>
	);
}

What’s this for?

  • Add a toggle button to your header or anywhere in your UI.
  • Use DrawerActions.toggleDrawer() to open/close the drawer programmatically.

Conclusion

And that’s it! You now have a fully functional, stylish drawer navigation in your Expo Router app. You can customize it, add dynamic items, and control it from anywhere in your app. This is a great foundation for any multi-page React Native project.

Want to see the full code? Check out the repo!

Happy coding! 🚀

Galaxies-dev/
expo-router-drawer-essentials

React Native Drawer Navigation with Expo Router
Galaxies Github Icon
Get this code Want to play around with this code locally? Download the code and see it all in context. Galaxies members get all the code & so much more. I can't access the code!
Zero to Hero React Native Mission
Simon Grimm