React Native Drawer Navigation with Expo Router
Last update: 2025-05-20Welcome! 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

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.
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.
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
.
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.
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
.
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:
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
