How to Add a Navigation Drawer Below the App Bar and Set a Custom Menu Icon in Flutter
A well-structured navigation system is the backbone of a great user experience in any mobile application. In Flutter, the Drawer
widget provides a convenient and familiar way for users to access different screens and features. While the default slide-in drawer is effective, you might have design requirements for a navigation menu that’s always visible or appears just below the app bar, especially on larger screens.
This comprehensive guide will walk you through everything you need to know about implementing navigation drawers in Flutter. We’ll start with the basics of adding a standard drawer and customizing its menu icon. Then, we’ll dive into the more advanced technique of creating a persistent navigation menu that sits below the app bar, a perfect solution for tablet and web layouts.
The Standard Flutter Navigation Drawer
Let’s begin with the conventional approach. The Scaffold
widget in Flutter has a drawer
property that makes it incredibly simple to add a slide-out navigation panel.
Here’s a basic implementation:
Dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('FlutterStuff.com'),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text(
'Menu Header',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
ListTile(
leading: const Icon(Icons.home),
title: const Text('Home'),
onTap: () {
// Navigate to home screen
},
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Settings'),
onTap: () {
// Navigate to settings screen
},
),
],
),
),
body: const Center(
child: Text('Your main content goes here!'),
),
),
);
}
}
DartIn this code, we’ve added a Drawer
to our Scaffold
. The DrawerHeader
provides a customizable space at the top, and the ListTile
widgets act as our menu items. By default, Flutter automatically adds a “hamburger” icon (three horizontal lines) to the AppBar
to open the drawer.
Customizing the Menu Icon
While the hamburger icon is universally recognized, you might want to use a custom icon to better match your app’s branding. You can achieve this by using the leading
property of the AppBar
.
Here’s how to set a custom menu icon:
Dart
appBar: AppBar(
title: const Text('FlutterStuff.com'),
leading: Builder(
builder: (BuildContext context) {
return IconButton(
icon: const Icon(Icons.menu_open_rounded), // Your custom icon
onPressed: () {
Scaffold.of(context).openDrawer();
},
tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
);
},
),
),
DartWe wrap the IconButton
in a Builder
widget to get a BuildContext
that is a descendant of the Scaffold
. This allows us to call Scaffold.of(context).openDrawer()
to open the drawer when the custom icon is tapped.
Creating a Persistent Navigation Menu Below the App Bar
For applications that run on larger screens like tablets or desktops, a persistent navigation menu that sits to the side of the main content can be more user-friendly than a slide-out drawer. This is often referred to as a “side menu” or a “navigation rail.”
To achieve this “below the app bar” effect, we’ll structure our UI using a Row
widget. This Row
will contain our navigation menu and the main content area.
Here’s a conceptual layout:
Dart
Scaffold(
appBar: AppBar(
title: const Text('FlutterStuff.com'),
),
body: Row(
children: [
// Your persistent navigation menu widget
const MyNavigationMenu(),
// A vertical divider for visual separation
const VerticalDivider(thickness: 1, width: 1),
// The main content area that will change based on selection
Expanded(
child: Container(
// Your page content
),
),
],
),
);
DartBuilding a Responsive Navigation Menu
A key consideration for this pattern is responsiveness. On smaller screens, a persistent side menu can cramp the UI. The ideal solution is to show the persistent menu on larger screens and revert to the standard Drawer
on smaller screens.
We can use a LayoutBuilder
to determine the available screen width and adjust our layout accordingly.
Here is a more complete, responsive example:
Dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'FlutterStuff Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: const ResponsiveNavigation(),
);
}
}
class ResponsiveNavigation extends StatefulWidget {
const ResponsiveNavigation({super.key});
@override
State<ResponsiveNavigation> createState() => _ResponsiveNavigationState();
}
class _ResponsiveNavigationState extends State<ResponsiveNavigation> {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('FlutterStuff.com'),
),
drawer: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return const AppDrawer();
} else {
return null;
}
},
),
body: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < 600) {
return _buildMainContent();
} else {
return Row(
children: [
NavigationRail(
selectedIndex: _selectedIndex,
onDestinationSelected: (int index) {
setState(() {
_selectedIndex = index;
});
},
labelType: NavigationRailLabelType.all,
destinations: const [
NavigationRailDestination(
icon: Icon(Icons.home_outlined),
selectedIcon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.explore_outlined),
selectedIcon: Icon(Icons.explore),
label: Text('Explore'),
),
NavigationRailDestination(
icon: Icon(Icons.person_outline),
selectedIcon: Icon(Icons.person),
label: Text('Profile'),
),
],
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(
child: _buildMainContent(),
),
],
);
}
},
),
);
}
Widget _buildMainContent() {
switch (_selectedIndex) {
case 0:
return const Center(child: Text('Home Content'));
case 1:
return const Center(child: Text('Explore Content'));
case 2:
return const Center(child: Text('Profile Content'));
default:
return const Center(child: Text('Home Content'));
}
}
}
class AppDrawer extends StatelessWidget {
const AppDrawer({super.key});
@override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text(
'Menu',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
ListTile(
leading: const Icon(Icons.home),
title: const Text('Home'),
onTap: () {
// Handle navigation
Navigator.pop(context); // Close the drawer
},
),
ListTile(
leading: const Icon(Icons.explore),
title: const Text('Explore'),
onTap: () {
// Handle navigation
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.person),
title: const Text('Profile'),
onTap: () {
// Handle navigation
Navigator.pop(context);
},
),
],
),
);
}
}
DartIn this example:
- We use a
LayoutBuilder
to check themaxWidth
. If it’s less than 600 pixels (a common breakpoint for mobile), we display the standardDrawer
. - On wider screens, we use a
Row
to display aNavigationRail
alongside the main content. TheNavigationRail
is a Material Design widget perfect for this purpose. - We manage the selected state with
_selectedIndex
to highlight the active destination in theNavigationRail
and to show the corresponding content.
Best Practices for Navigation Drawers
- Keep it Concise: Avoid cluttering your navigation drawer with too many items. Prioritize the most important destinations in your app.
- Provide Visual Feedback: Clearly indicate the currently active screen in the drawer, as shown in the
NavigationRail
example withselectedIndex
. - Consistent Design: Ensure the design of your drawer aligns with the overall theme and branding of your application.
- Think Responsively: As demonstrated, adapt your navigation pattern based on the screen size for an optimal user experience across all devices. For more in-depth tutorials and Flutter resources, be sure to check out other articles on FlutterStuff.com.
By understanding both the standard Drawer
and how to create a custom, persistent navigation menu, you have the flexibility to build intuitive and user-friendly navigation experiences for any Flutter application.