Drawer Navigation
Drawer Navigation in Flutter
A Drawer is a side panel that slides in from the left (or right) edge of the screen, providing access to primary navigation destinations in your app. It is a Material Design pattern that works especially well on mobile devices where screen space is limited. Flutter makes it simple to add a fully functional drawer to any Scaffold by setting its drawer property.
Drawers are ideal when your app has five or more top-level destinations that would otherwise clutter a bottom navigation bar, or when you want to keep the main content area clean and distraction-free. The user opens the drawer by swiping from the left edge of the screen or tapping the hamburger icon that Flutter automatically places in the AppBar.
AppBar when a drawer is provided to the Scaffold. You do not need to add the icon manually. Use endDrawer instead of drawer if you want the panel to slide in from the right.Basic Drawer Structure
A drawer is built using the Drawer widget, which typically wraps a ListView containing a DrawerHeader (or UserAccountsDrawerHeader) followed by a series of ListTile widgets — one for each navigation destination.
Minimal Drawer Example
import 'package:flutter/material.dart';
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const HomeScreen(),
'/profile': (context) => const ProfileScreen(),
'/settings': (context) => const SettingsScreen(),
},
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Home')),
drawer: Drawer(
child: ListView(
// Remove the default top padding so DrawerHeader sits flush
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
decoration: BoxDecoration(color: Colors.indigo),
child: Text(
'My App',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
ListTile(
leading: const Icon(Icons.home),
title: const Text('Home'),
onTap: () {
Navigator.pop(context); // Close the drawer first
Navigator.pushReplacementNamed(context, '/');
},
),
ListTile(
leading: const Icon(Icons.person),
title: const Text('Profile'),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacementNamed(context, '/profile');
},
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Settings'),
onTap: () {
Navigator.pop(context);
Navigator.pushReplacementNamed(context, '/settings');
},
),
],
),
),
body: const Center(child: Text('Home Screen')),
);
}
}
Navigator.pop(context) before navigating to close the drawer smoothly. If you navigate without closing, the drawer will remain on top of the new screen until the user manually dismisses it.Highlighting the Active Destination
Good UX requires the drawer to visually indicate which screen the user is currently on. You can achieve this by tracking a selected index (or selected route name) in a StatefulWidget and using the selected property of ListTile along with selectedTileColor to highlight the active item.
Drawer with Active Item Highlighting
class MainScaffold extends StatefulWidget {
const MainScaffold({super.key});
@override
State<MainScaffold> createState() => _MainScaffoldState();
}
class _MainScaffoldState extends State<MainScaffold> {
// Track which destination is currently active
int _selectedIndex = 0;
// Parallel lists: screens and their drawer metadata
final List<Widget> _screens = const [
HomeScreen(),
ProfileScreen(),
SettingsScreen(),
];
final List<Map<String, dynamic>> _navItems = const [
{'icon': Icons.home, 'label': 'Home'},
{'icon': Icons.person, 'label': 'Profile'},
{'icon': Icons.settings, 'label': 'Settings'},
];
void _navigateTo(int index) {
setState(() {
_selectedIndex = index;
});
Navigator.pop(context); // Close drawer
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_navItems[_selectedIndex]['label'] as String),
backgroundColor: Colors.indigo,
foregroundColor: Colors.white,
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
UserAccountsDrawerHeader(
accountName: const Text('Edrees Salih'),
accountEmail: const Text('edrees@example.com'),
currentAccountPicture: const CircleAvatar(
backgroundColor: Colors.white,
child: Icon(Icons.person, color: Colors.indigo, size: 40),
),
decoration: const BoxDecoration(color: Colors.indigo),
),
...List.generate(_navItems.length, (index) {
final item = _navItems[index];
return ListTile(
leading: Icon(item['icon'] as IconData),
title: Text(item['label'] as String),
selected: _selectedIndex == index,
selectedTileColor: Colors.indigo.withOpacity(0.12),
selectedColor: Colors.indigo,
onTap: () => _navigateTo(index),
);
}),
const Divider(),
ListTile(
leading: const Icon(Icons.logout),
title: const Text('Sign Out'),
onTap: () {
Navigator.pop(context);
// Handle sign-out logic
},
),
],
),
),
body: _screens[_selectedIndex],
);
}
}
UserAccountsDrawerHeader
The UserAccountsDrawerHeader widget is a Material-standard drawer header designed to display a user's avatar, name, and email address. It automatically handles tap-to-switch-account interaction and adapts to the app's theme. Key properties include:
accountName— the primary user name widgetaccountEmail— the email or secondary text widgetcurrentAccountPicture— usually aCircleAvatardecoration— background, gradient, or image for the headerotherAccountsPictures— small avatars for additional accounts
Opening and Closing the Drawer Programmatically
Sometimes you need to open or close the drawer from code rather than through user interaction. Use a GlobalKey<ScaffoldState> to get a reference to the Scaffold and call openDrawer() or closeDrawer().
Programmatic Drawer Control
class MyPage extends StatelessWidget {
// Assign a key to the Scaffold
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
MyPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Dashboard'),
// Custom button to open the drawer
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () => _scaffoldKey.currentState?.openDrawer(),
),
),
drawer: const Drawer(
child: Center(child: Text('Navigation Here')),
),
body: Center(
child: ElevatedButton(
onPressed: () => _scaffoldKey.currentState?.openDrawer(),
child: const Text('Open Menu'),
),
),
);
}
}
leading widget and a drawer to the Scaffold without also assigning an automaticallyImplyLeading: false override if you want full control. By default, Flutter will show the hamburger icon automatically and your custom leading widget will replace it.Drawer Width and Appearance
The default drawer width is 304 logical pixels (per Material guidelines). You can customise the appearance through the Drawer properties:
width— set a custom width (e.g.280)backgroundColor— background color of the drawer panelshape— rounded corners or other border shapeselevation— shadow depth (default 16)
Summary
The Scaffold drawer property accepts any widget, but the conventional pattern is Drawer → ListView (with padding: EdgeInsets.zero) → header widget + ListTile items. Track the selected destination index in a StatefulWidget, pass it to each ListTile's selected property, and call Navigator.pop(context) before navigating to give a smooth user experience. For programmatic control, use a GlobalKey<ScaffoldState>.