Flutter's Drawer widget provides a slide-in side navigation panel — assigning it to a Scaffold's drawer property is most of the work, though a couple of details around the hamburger icon and closing behavior are worth understanding.
A basic drawer
Scaffold(
appBar: AppBar(title: const Text('My App')),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
decoration: BoxDecoration(color: Colors.deepPurple),
child: Text('Menu', style: TextStyle(color: Colors.white, fontSize: 24)),
),
ListTile(
leading: const Icon(Icons.home),
title: const Text('Home'),
onTap: () {
Navigator.pop(context); // close the drawer
// navigate to home
},
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Settings'),
onTap: () {
Navigator.pop(context);
// navigate to settings
},
),
],
),
),
body: const Center(child: Text('Main content')),
)
Assigning a Drawer to the Scaffold's drawer property automatically adds a hamburger menu icon to the AppBar — but only if an AppBar is actually present; without one, the drawer can still be opened by swiping from the screen edge, but there's no tappable icon for it.
Closing the drawer after tapping a menu item
onTap: () {
Navigator.pop(context);
Navigator.pushNamed(context, '/settings');
}
Navigator.pop(context) here closes the drawer itself (the drawer counts as a route on the navigation stack) — this needs to happen before or alongside navigating to the new screen, or the drawer stays open, incorrectly overlapping the newly navigated-to content.
A drawer with a user profile header
UserAccountsDrawerHeader(
accountName: const Text('Alex Johnson'),
accountEmail: const Text('alex@example.com'),
currentAccountPicture: const CircleAvatar(
backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
),
decoration: const BoxDecoration(color: Colors.deepPurple),
)
UserAccountsDrawerHeader is a purpose-built alternative to the plain DrawerHeader specifically for showing a logged-in user's name, email, and avatar — a common enough pattern that Flutter provides this dedicated widget rather than requiring it to be built manually every time.
A drawer on the right side, instead of the left
Scaffold(
endDrawer: Drawer(
child: ListView(children: const [/* ... */]),
),
)
endDrawer, rather than drawer, opens from the trailing (right, in a left-to-right layout) edge of the screen instead — a common pattern for a secondary panel like filters or notifications, while the primary navigation drawer stays on the leading edge.
Opening the drawer programmatically, without user interaction
final GlobalKey _scaffoldKey = GlobalKey();
Scaffold(
key: _scaffoldKey,
drawer: Drawer(child: /* ... */),
)
_scaffoldKey.currentState?.openDrawer();
Assigning a GlobalKey<ScaffoldState> to the Scaffold allows opening the drawer programmatically (from a custom button rather than the automatic hamburger icon, for example) — necessary since there's no direct method to open a drawer without a reference to its Scaffold's state.