Skip to main content

Drawer Navigation

Drawer navigation provides a panel that slides in from the side of the screen, typically used for app-wide navigation and settings.

Installation

Install required dependencies:
npx expo install react-native-gesture-handler react-native-reanimated @react-navigation/drawer
From package.json:84-123:
"peerDependencies": {
  "@react-navigation/drawer": "^7.7.2",
  "react-native-gesture-handler": "*",
  "react-native-reanimated": "*"
},
"peerDependenciesMeta": {
  "@react-navigation/drawer": {
    "optional": true
  },
  "react-native-gesture-handler": {
    "optional": true
  },
  "react-native-reanimated": {
    "optional": true
  }
}

Basic Drawer

Create a drawer layout:
app/_layout.tsx
import { Drawer } from 'expo-router/drawer';

export default function Layout() {
  return <Drawer />;
}
Add drawer screens:
app/
  _layout.tsx
  index.tsx         # Home screen
  profile.tsx       # Profile screen
  settings.tsx      # Settings screen
From the source (Drawer.tsx:1-9):
import Drawer from './DrawerClient';
import { Screen } from '../views/Screen';

Drawer.Screen = Screen;

export { Drawer };
export default Drawer;

Configure Drawer

Customize drawer appearance:
app/_layout.tsx
import { Drawer } from 'expo-router/drawer';
import { Ionicons } from '@expo/vector-icons';

export default function Layout() {
  return (
    <Drawer
      screenOptions={{
        drawerStyle: {
          backgroundColor: '#fff',
          width: 280,
        },
        drawerActiveTintColor: '#f4511e',
        drawerInactiveTintColor: '#666',
        drawerLabelStyle: {
          marginLeft: -16,
          fontSize: 16,
        },
      }}
    >
      <Drawer.Screen
        name="index"
        options={{
          drawerLabel: 'Home',
          title: 'Home',
          drawerIcon: ({ color, size }) => (
            <Ionicons name="home" size={size} color={color} />
          ),
        }}
      />
      <Drawer.Screen
        name="profile"
        options={{
          drawerLabel: 'Profile',
          title: 'Profile',
          drawerIcon: ({ color, size }) => (
            <Ionicons name="person" size={size} color={color} />
          ),
        }}
      />
      <Drawer.Screen
        name="settings"
        options={{
          drawerLabel: 'Settings',
          title: 'Settings',
          drawerIcon: ({ color, size }) => (
            <Ionicons name="settings" size={size} color={color} />
          ),
        }}
      />
    </Drawer>
  );
}

Drawer Icons

Icon Library

import { MaterialIcons, FontAwesome } from '@expo/vector-icons';

<Drawer.Screen
  name="home"
  options={{
    drawerIcon: ({ color, size }) => (
      <MaterialIcons name="dashboard" size={size} color={color} />
    ),
  }}
/>

Custom Icons

import { Image } from 'react-native';

<Drawer.Screen
  name="home"
  options={{
    drawerIcon: ({ focused }) => (
      <Image
        source={focused ? require('./icon-active.png') : require('./icon.png')}
        style={{ width: 24, height: 24 }}
      />
    ),
  }}
/>

Drawer Position

<Drawer
  screenOptions={{
    drawerPosition: 'left', // or 'right'
  }}
/>

Drawer Type

Slide (Default)

<Drawer
  screenOptions={{
    drawerType: 'slide',
  }}
/>

Front

<Drawer
  screenOptions={{
    drawerType: 'front',
  }}
/>

Back

<Drawer
  screenOptions={{
    drawerType: 'back',
  }}
/>

Permanent

<Drawer
  screenOptions={{
    drawerType: 'permanent',
  }}
/>

Custom Drawer Content

Create custom drawer:
app/_layout.tsx
import { Drawer } from 'expo-router/drawer';
import {
  DrawerContentScrollView,
  DrawerItemList,
  DrawerItem,
} from '@react-navigation/drawer';
import { View, Text, Image } from 'react-native';

function CustomDrawerContent(props) {
  return (
    <DrawerContentScrollView {...props}>
      <View style={{ padding: 20, borderBottomWidth: 1, borderBottomColor: '#ddd' }}>
        <Image
          source={{ uri: 'https://example.com/avatar.jpg' }}
          style={{ width: 60, height: 60, borderRadius: 30 }}
        />
        <Text style={{ marginTop: 10, fontSize: 18, fontWeight: 'bold' }}>
          John Doe
        </Text>
        <Text style={{ color: '#666' }}>john@example.com</Text>
      </View>
      
      <DrawerItemList {...props} />
      
      <DrawerItem
        label="Logout"
        onPress={() => handleLogout()}
        icon={({ color, size }) => (
          <Ionicons name="log-out" size={size} color={color} />
        )}
      />
    </DrawerContentScrollView>
  );
}

export default function Layout() {
  return (
    <Drawer
      drawerContent={(props) => <CustomDrawerContent {...props} />}
    >
      <Drawer.Screen name="index" options={{ title: 'Home' }} />
      <Drawer.Screen name="profile" options={{ title: 'Profile' }} />
    </Drawer>
  );
}

Opening the Drawer

Toggle Drawer

import { useNavigation } from 'expo-router';
import { DrawerActions } from '@react-navigation/native';
import { Button } from 'react-native';

export default function Screen() {
  const navigation = useNavigation();
  
  return (
    <Button
      title="Open Drawer"
      onPress={() => navigation.dispatch(DrawerActions.openDrawer())}
    />
  );
}

Custom Header Button

app/_layout.tsx
import { Drawer } from 'expo-router/drawer';
import { Pressable } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useNavigation } from 'expo-router';
import { DrawerActions } from '@react-navigation/native';

function DrawerToggle() {
  const navigation = useNavigation();
  
  return (
    <Pressable
      onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
      style={{ marginLeft: 16 }}
    >
      <Ionicons name="menu" size={24} />
    </Pressable>
  );
}

export default function Layout() {
  return (
    <Drawer
      screenOptions={{
        headerLeft: () => <DrawerToggle />,
      }}
    />
  );
}

Drawer with Tabs

Combine drawer with tabs:
app/
  _layout.tsx           # Drawer layout
  (tabs)/
    _layout.tsx         # Tabs layout
    index.tsx
    profile.tsx
  settings.tsx          # Drawer screen
  about.tsx             # Drawer screen
app/_layout.tsx
import { Drawer } from 'expo-router/drawer';

export default function RootLayout() {
  return (
    <Drawer>
      <Drawer.Screen
        name="(tabs)"
        options={{
          drawerLabel: 'Home',
          title: 'Home',
        }}
      />
      <Drawer.Screen
        name="settings"
        options={{
          drawerLabel: 'Settings',
          title: 'Settings',
        }}
      />
      <Drawer.Screen
        name="about"
        options={{
          drawerLabel: 'About',
          title: 'About',
        }}
      />
    </Drawer>
  );
}

Gesture Configuration

<Drawer
  screenOptions={{
    swipeEnabled: true,
    swipeEdgeWidth: 50,
    gestureHandlerProps: {
      enableTrackpadTwoFingerGesture: true,
    },
  }}
/>

Hide Drawer Items

<Drawer.Screen
  name="hidden"
  options={{
    drawerItemStyle: { display: 'none' },
  }}
/>

Drawer Sections

Group drawer items:
function CustomDrawerContent(props) {
  return (
    <DrawerContentScrollView {...props}>
      <View>
        <Text style={{ padding: 16, fontWeight: 'bold' }}>Main</Text>
        <DrawerItem label="Home" onPress={() => {}} />
        <DrawerItem label="Explore" onPress={() => {}} />
      </View>
      
      <View>
        <Text style={{ padding: 16, fontWeight: 'bold' }}>Account</Text>
        <DrawerItem label="Profile" onPress={() => {}} />
        <DrawerItem label="Settings" onPress={() => {}} />
      </View>
      
      <View>
        <Text style={{ padding: 16, fontWeight: 'bold' }}>Other</Text>
        <DrawerItem label="Help" onPress={() => {}} />
        <DrawerItem label="About" onPress={() => {}} />
      </View>
    </DrawerContentScrollView>
  );
}

Drawer Badge

<Drawer.Screen
  name="notifications"
  options={{
    drawerLabel: 'Notifications',
    drawerBadge: 3,
    drawerBadgeStyle: {
      backgroundColor: '#f4511e',
    },
  }}
/>

Responsive Drawer

import { useWindowDimensions } from 'react-native';

export default function Layout() {
  const dimensions = useWindowDimensions();
  const isLargeScreen = dimensions.width >= 768;
  
  return (
    <Drawer
      screenOptions={{
        drawerType: isLargeScreen ? 'permanent' : 'slide',
        drawerStyle: {
          width: isLargeScreen ? 280 : 240,
        },
      }}
    />
  );
}

Common Patterns

User Profile Header

function CustomDrawerContent(props) {
  const { user } = useAuth();
  
  return (
    <DrawerContentScrollView {...props}>
      <Pressable
        onPress={() => router.push('/profile')}
        style={styles.profileHeader}
      >
        <Image source={{ uri: user.avatar }} style={styles.avatar} />
        <View>
          <Text style={styles.name}>{user.name}</Text>
          <Text style={styles.email}>{user.email}</Text>
        </View>
      </Pressable>
      
      <DrawerItemList {...props} />
    </DrawerContentScrollView>
  );
}

Logout Button

function CustomDrawerContent(props) {
  const { logout } = useAuth();
  
  return (
    <DrawerContentScrollView {...props}>
      <DrawerItemList {...props} />
      
      <View style={{ marginTop: 'auto' }}>
        <DrawerItem
          label="Logout"
          onPress={logout}
          icon={({ color, size }) => (
            <Ionicons name="log-out" size={size} color={color} />
          )}
          labelStyle={{ color: '#f44336' }}
        />
      </View>
    </DrawerContentScrollView>
  );
}

Theme Toggle

function CustomDrawerContent(props) {
  const { theme, toggleTheme } = useTheme();
  
  return (
    <DrawerContentScrollView {...props}>
      <DrawerItemList {...props} />
      
      <DrawerItem
        label={theme === 'light' ? 'Dark Mode' : 'Light Mode'}
        onPress={toggleTheme}
        icon={({ size }) => (
          <Ionicons
            name={theme === 'light' ? 'moon' : 'sunny'}
            size={size}
          />
        )}
      />
    </DrawerContentScrollView>
  );
}

Best Practices

Use for Top-Level Navigation

// Good: Top-level app sections
<Drawer>
  <Drawer.Screen name="home" />
  <Drawer.Screen name="discover" />
  <Drawer.Screen name="settings" />
</Drawer>

// Avoid: Too many nested levels
<Drawer>
  <Drawer.Screen name="section1/subsection1/page1" />
</Drawer>

Combine with Other Navigators

// Good: Drawer for main navigation, tabs/stacks within
<Drawer>
  <Drawer.Screen name="(tabs)" />  {/* Tabs inside drawer */}
  <Drawer.Screen name="settings" />
</Drawer>

Clear Labels and Icons

// Good: Descriptive labels with appropriate icons
<Drawer.Screen
  name="home"
  options={{
    drawerLabel: 'Home',
    drawerIcon: ({ color }) => <Ionicons name="home" color={color} />,
  }}
/>

Next Steps

Stack Navigation

Combine with stack navigation

Tab Navigation

Alternative to drawer

Layouts

Understand drawer layouts

Navigation

Navigate from drawer