Navigation Drawers in Flutter: From Basics to Adaptive Patterns
Build Material navigation drawers with Scaffold, DrawerHeader, endDrawer, and an adaptive NavigationRail strategy for wide screens.
Published on • November 2, 2026
AI Assistant

Secondary navigation — settings, profile, saved items — needs somewhere to live without stealing horizontal space, and the Material navigation drawer is Flutter’s default answer (docs.flutter.dev).
The Basics: Scaffold.drawer
Scaffold(
appBar: AppBar(title: const Text('Home')),
drawer: const AppDrawer(), // the app bar then shows the hamburger icon
)
Headers: DrawerHeader and UserAccountsDrawerHeader
Use DrawerHeader for branding, or UserAccountsDrawerHeader when you have a photo, name, and email:
Drawer(
child: ListView(padding: EdgeInsets.zero, children: [
const DrawerHeader(decoration: BoxDecoration(color: Colors.indigo),
child: Text('RedlineSoft Blog', style: TextStyle(color: Colors.white, fontSize: 24))),
ListTile(leading: const Icon(Icons.article_outlined),
title: const Text('Articles'), onTap: () => _open(context, '/articles')),
ListTile(leading: const Icon(Icons.settings_outlined),
title: const Text('Settings'), onTap: () => _open(context, '/settings')),
]),
)
Controlling the Drawer: Close, Navigate, Open
An item must close first, then navigate — skipping Navigator.pop leaves the drawer open over the new route. endDrawer mirrors this on the right; for manual control use Scaffold.of(context):
void _open(BuildContext context, String route) {
Navigator.pop(context); // close the drawer
Navigator.pushNamed(context, route);
}
final scaffold = Scaffold.of(context);
scaffold.isEndDrawerOpen ? scaffold.closeEndDrawer() : scaffold.openDrawer();
Adaptive Strategy: Drawer on Narrow, NavigationRail on Wide
LayoutBuilder(builder: (context, constraints) {
final wide = constraints.maxWidth >= 800;
return Scaffold(
appBar: AppBar(title: const Text('Home')),
drawer: wide ? null : const AppDrawer(), // hide on wide screens
body: Row(children: [
if (wide)
NavigationRail(
selectedIndex: _index,
onDestinationSelected: (i) => setState(() => _index = i),
destinations: const [
NavigationRailDestination(icon: Icon(Icons.article_outlined), label: Text('Articles')),
NavigationRailDestination(icon: Icon(Icons.settings_outlined), label: Text('Settings')),
]),
const Expanded(child: HomeBody()),
]),
);
});
Gotchas
Navigator.popbefore navigating — the single most common drawer bug.- Give every drawer
ListTileaselectedstate tied to the current route, and handle inset padding (SafeAreaorpadding: EdgeInsets.zero) so content clears the status bar.
Takeaway: wire Scaffold.drawer for phones, swap in a NavigationRail past a width breakpoint, and always pop the drawer before you push the route.