Flutter Drawer Localization: Navigation Menus for Multilingual Apps
Drawer is a Flutter Material Design widget that slides in from the edge of the screen to display navigation links. In multilingual applications, Drawer provides consistent navigation menus that automatically adapt to RTL languages, appearing from the appropriate side of the screen.
Understanding Drawer in Localization Context
Drawer creates a panel that slides in from the screen edge, containing navigation options and user account information. For multilingual apps, this enables:
- Automatic RTL positioning (opens from right in RTL languages)
- Localized menu items and headers
- Direction-aware content alignment
- Consistent navigation patterns across all locales
Why Drawer Matters for Multilingual Apps
Drawer provides:
- RTL support: Automatically opens from the correct side
- Flexible content: Adapts to translated text lengths
- Standard navigation: Familiar pattern for all users
- Theme integration: Respects app-wide localized themes
Basic Drawer Implementation
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedDrawerExample extends StatelessWidget {
const LocalizedDrawerExample({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.appTitle),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
CircleAvatar(
radius: 32,
child: Text(l10n.userName[0].toUpperCase()),
),
const SizedBox(height: 12),
Text(
l10n.userName,
style: Theme.of(context).textTheme.titleMedium,
),
Text(
l10n.userEmail,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
ListTile(
leading: const Icon(Icons.home),
title: Text(l10n.menuHome),
onTap: () => Navigator.pop(context),
),
ListTile(
leading: const Icon(Icons.favorite),
title: Text(l10n.menuFavorites),
onTap: () => Navigator.pop(context),
),
ListTile(
leading: const Icon(Icons.settings),
title: Text(l10n.menuSettings),
onTap: () => Navigator.pop(context),
),
const Divider(),
ListTile(
leading: const Icon(Icons.logout),
title: Text(l10n.menuLogout),
onTap: () => Navigator.pop(context),
),
],
),
),
body: Center(
child: Text(l10n.mainContent),
),
);
}
}
User Account Drawer Header
Localized Account Header
class LocalizedUserAccountDrawer extends StatelessWidget {
const LocalizedUserAccountDrawer({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.appTitle)),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
UserAccountsDrawerHeader(
accountName: Text(l10n.userName),
accountEmail: Text(l10n.userEmail),
currentAccountPicture: CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.primary,
child: Text(
l10n.userName[0].toUpperCase(),
style: TextStyle(
fontSize: 32,
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
otherAccountsPictures: [
CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.secondary,
child: const Text('W'),
),
CircleAvatar(
backgroundColor: Theme.of(context).colorScheme.tertiary,
child: const Text('P'),
),
],
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
),
ListTile(
leading: const Icon(Icons.inbox),
title: Text(l10n.menuInbox),
trailing: Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
borderRadius: BorderRadius.circular(12),
),
child: Text(
'12',
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
fontSize: 12,
),
),
),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.star),
title: Text(l10n.menuStarred),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.send),
title: Text(l10n.menuSent),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.drafts),
title: Text(l10n.menuDrafts),
onTap: () {},
),
],
),
),
body: Center(child: Text(l10n.mainContent)),
);
}
}
Grouped Menu Items
Drawer with Sections
class LocalizedSectionedDrawer extends StatelessWidget {
const LocalizedSectionedDrawer({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.appTitle)),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
DrawerHeader(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context).colorScheme.primary,
Theme.of(context).colorScheme.secondary,
],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
l10n.appTitle,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: Colors.white,
),
),
Text(
l10n.appTagline,
style: TextStyle(color: Colors.white70),
),
],
),
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Text(
l10n.sectionMain,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
),
),
ListTile(
leading: const Icon(Icons.dashboard),
title: Text(l10n.menuDashboard),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.analytics),
title: Text(l10n.menuAnalytics),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.people),
title: Text(l10n.menuTeam),
onTap: () {},
),
const Divider(),
Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
child: Text(
l10n.sectionAccount,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
),
),
ListTile(
leading: const Icon(Icons.person),
title: Text(l10n.menuProfile),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.settings),
title: Text(l10n.menuSettings),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.help),
title: Text(l10n.menuHelp),
onTap: () {},
),
],
),
),
body: Center(child: Text(l10n.mainContent)),
);
}
}
Expandable Menu Items
Drawer with Expansion Tiles
class LocalizedExpandableDrawer extends StatelessWidget {
const LocalizedExpandableDrawer({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.appTitle)),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
child: Text(
l10n.navigationTitle,
style: Theme.of(context).textTheme.headlineSmall,
),
),
ListTile(
leading: const Icon(Icons.home),
title: Text(l10n.menuHome),
onTap: () {},
),
ExpansionTile(
leading: const Icon(Icons.category),
title: Text(l10n.menuCategories),
children: [
ListTile(
contentPadding: const EdgeInsetsDirectional.only(start: 72),
title: Text(l10n.categoryElectronics),
onTap: () {},
),
ListTile(
contentPadding: const EdgeInsetsDirectional.only(start: 72),
title: Text(l10n.categoryClothing),
onTap: () {},
),
ListTile(
contentPadding: const EdgeInsetsDirectional.only(start: 72),
title: Text(l10n.categoryBooks),
onTap: () {},
),
],
),
ExpansionTile(
leading: const Icon(Icons.account_circle),
title: Text(l10n.menuAccount),
children: [
ListTile(
contentPadding: const EdgeInsetsDirectional.only(start: 72),
title: Text(l10n.accountOrders),
onTap: () {},
),
ListTile(
contentPadding: const EdgeInsetsDirectional.only(start: 72),
title: Text(l10n.accountWishlist),
onTap: () {},
),
ListTile(
contentPadding: const EdgeInsetsDirectional.only(start: 72),
title: Text(l10n.accountAddresses),
onTap: () {},
),
],
),
ListTile(
leading: const Icon(Icons.settings),
title: Text(l10n.menuSettings),
onTap: () {},
),
],
),
),
body: Center(child: Text(l10n.mainContent)),
);
}
}
Selected State
Drawer with Active Selection
class LocalizedSelectedDrawer extends StatefulWidget {
const LocalizedSelectedDrawer({super.key});
@override
State<LocalizedSelectedDrawer> createState() =>
_LocalizedSelectedDrawerState();
}
class _LocalizedSelectedDrawerState extends State<LocalizedSelectedDrawer> {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final menuItems = [
(Icons.home, l10n.menuHome),
(Icons.explore, l10n.menuExplore),
(Icons.subscriptions, l10n.menuSubscriptions),
(Icons.video_library, l10n.menuLibrary),
];
return Scaffold(
appBar: AppBar(title: Text(l10n.appTitle)),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const SizedBox(height: 48),
...List.generate(menuItems.length, (index) {
final (icon, title) = menuItems[index];
final isSelected = index == _selectedIndex;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
decoration: BoxDecoration(
color: isSelected
? Theme.of(context).colorScheme.primaryContainer
: null,
borderRadius: BorderRadius.circular(28),
),
child: ListTile(
leading: Icon(
icon,
color: isSelected
? Theme.of(context).colorScheme.primary
: null,
),
title: Text(
title,
style: TextStyle(
color: isSelected
? Theme.of(context).colorScheme.primary
: null,
fontWeight: isSelected ? FontWeight.bold : null,
),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(28),
),
onTap: () {
setState(() => _selectedIndex = index);
Navigator.pop(context);
},
),
);
}),
const Divider(indent: 28, endIndent: 28),
ListTile(
leading: const Icon(Icons.settings),
title: Text(l10n.menuSettings),
onTap: () => Navigator.pop(context),
),
],
),
),
body: Center(
child: Text('${menuItems[_selectedIndex].$2} ${l10n.screenContent}'),
),
);
}
}
End Drawer
Right-Side Drawer
class LocalizedEndDrawer extends StatelessWidget {
const LocalizedEndDrawer({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.appTitle),
actions: [
Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.tune),
tooltip: l10n.filtersTooltip,
onPressed: () {
Scaffold.of(context).openEndDrawer();
},
),
),
],
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
DrawerHeader(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer,
),
child: Text(
l10n.navigationTitle,
style: Theme.of(context).textTheme.headlineSmall,
),
),
ListTile(
leading: const Icon(Icons.home),
title: Text(l10n.menuHome),
onTap: () => Navigator.pop(context),
),
],
),
),
endDrawer: Drawer(
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
l10n.filtersTitle,
style: Theme.of(context).textTheme.titleLarge,
),
TextButton(
onPressed: () {},
child: Text(l10n.clearFilters),
),
],
),
),
const Divider(),
Padding(
padding: const EdgeInsets.all(16),
child: Text(
l10n.priceRange,
style: Theme.of(context).textTheme.titleMedium,
),
),
RangeSlider(
values: const RangeValues(20, 80),
min: 0,
max: 100,
onChanged: (_) {},
),
Padding(
padding: const EdgeInsets.all(16),
child: Text(
l10n.sortBy,
style: Theme.of(context).textTheme.titleMedium,
),
),
RadioListTile(
title: Text(l10n.sortNewest),
value: 'newest',
groupValue: 'newest',
onChanged: (_) {},
),
RadioListTile(
title: Text(l10n.sortPriceLow),
value: 'price_low',
groupValue: 'newest',
onChanged: (_) {},
),
RadioListTile(
title: Text(l10n.sortPriceHigh),
value: 'price_high',
groupValue: 'newest',
onChanged: (_) {},
),
const Spacer(),
Padding(
padding: const EdgeInsets.all(16),
child: FilledButton(
onPressed: () => Navigator.pop(context),
child: Text(l10n.applyFilters),
),
),
],
),
),
),
body: Center(child: Text(l10n.mainContent)),
);
}
}
ARB File Structure
English (app_en.arb)
{
"@@locale": "en",
"appTitle": "My App",
"userName": "John Doe",
"userEmail": "john@example.com",
"menuHome": "Home",
"menuFavorites": "Favorites",
"menuSettings": "Settings",
"menuLogout": "Log Out",
"mainContent": "Main content area",
"menuInbox": "Inbox",
"menuStarred": "Starred",
"menuSent": "Sent",
"menuDrafts": "Drafts",
"appTagline": "Your daily companion",
"sectionMain": "MAIN",
"sectionAccount": "ACCOUNT",
"menuDashboard": "Dashboard",
"menuAnalytics": "Analytics",
"menuTeam": "Team",
"menuProfile": "Profile",
"menuHelp": "Help",
"navigationTitle": "Navigation",
"menuCategories": "Categories",
"categoryElectronics": "Electronics",
"categoryClothing": "Clothing",
"categoryBooks": "Books",
"menuAccount": "Account",
"accountOrders": "Orders",
"accountWishlist": "Wishlist",
"accountAddresses": "Addresses",
"menuExplore": "Explore",
"menuSubscriptions": "Subscriptions",
"menuLibrary": "Library",
"screenContent": "Screen",
"filtersTooltip": "Filters",
"filtersTitle": "Filters",
"clearFilters": "Clear All",
"priceRange": "Price Range",
"sortBy": "Sort By",
"sortNewest": "Newest First",
"sortPriceLow": "Price: Low to High",
"sortPriceHigh": "Price: High to Low",
"applyFilters": "Apply Filters"
}
German (app_de.arb)
{
"@@locale": "de",
"appTitle": "Meine App",
"userName": "Max Mustermann",
"userEmail": "max@example.com",
"menuHome": "Startseite",
"menuFavorites": "Favoriten",
"menuSettings": "Einstellungen",
"menuLogout": "Abmelden",
"mainContent": "Hauptinhaltsbereich",
"menuInbox": "Posteingang",
"menuStarred": "Markiert",
"menuSent": "Gesendet",
"menuDrafts": "Entwürfe",
"appTagline": "Ihr täglicher Begleiter",
"sectionMain": "HAUPTMENÜ",
"sectionAccount": "KONTO",
"menuDashboard": "Dashboard",
"menuAnalytics": "Analysen",
"menuTeam": "Team",
"menuProfile": "Profil",
"menuHelp": "Hilfe",
"navigationTitle": "Navigation",
"menuCategories": "Kategorien",
"categoryElectronics": "Elektronik",
"categoryClothing": "Kleidung",
"categoryBooks": "Bücher",
"menuAccount": "Konto",
"accountOrders": "Bestellungen",
"accountWishlist": "Wunschliste",
"accountAddresses": "Adressen",
"menuExplore": "Entdecken",
"menuSubscriptions": "Abonnements",
"menuLibrary": "Bibliothek",
"screenContent": "Bildschirm",
"filtersTooltip": "Filter",
"filtersTitle": "Filter",
"clearFilters": "Alle löschen",
"priceRange": "Preisbereich",
"sortBy": "Sortieren nach",
"sortNewest": "Neueste zuerst",
"sortPriceLow": "Preis: Niedrig bis Hoch",
"sortPriceHigh": "Preis: Hoch bis Niedrig",
"applyFilters": "Filter anwenden"
}
Arabic (app_ar.arb)
{
"@@locale": "ar",
"appTitle": "تطبيقي",
"userName": "أحمد محمد",
"userEmail": "ahmed@example.com",
"menuHome": "الرئيسية",
"menuFavorites": "المفضلة",
"menuSettings": "الإعدادات",
"menuLogout": "تسجيل الخروج",
"mainContent": "منطقة المحتوى الرئيسية",
"menuInbox": "البريد الوارد",
"menuStarred": "المميز بنجمة",
"menuSent": "المرسل",
"menuDrafts": "المسودات",
"appTagline": "رفيقك اليومي",
"sectionMain": "الرئيسية",
"sectionAccount": "الحساب",
"menuDashboard": "لوحة التحكم",
"menuAnalytics": "التحليلات",
"menuTeam": "الفريق",
"menuProfile": "الملف الشخصي",
"menuHelp": "المساعدة",
"navigationTitle": "التنقل",
"menuCategories": "الفئات",
"categoryElectronics": "الإلكترونيات",
"categoryClothing": "الملابس",
"categoryBooks": "الكتب",
"menuAccount": "الحساب",
"accountOrders": "الطلبات",
"accountWishlist": "قائمة الأمنيات",
"accountAddresses": "العناوين",
"menuExplore": "استكشاف",
"menuSubscriptions": "الاشتراكات",
"menuLibrary": "المكتبة",
"screenContent": "الشاشة",
"filtersTooltip": "الفلاتر",
"filtersTitle": "الفلاتر",
"clearFilters": "مسح الكل",
"priceRange": "نطاق السعر",
"sortBy": "ترتيب حسب",
"sortNewest": "الأحدث أولاً",
"sortPriceLow": "السعر: من الأقل إلى الأعلى",
"sortPriceHigh": "السعر: من الأعلى إلى الأقل",
"applyFilters": "تطبيق الفلاتر"
}
Best Practices Summary
Do's
- Let Flutter handle RTL drawer positioning automatically
- Use EdgeInsetsDirectional for directional padding in sub-items
- Include user account headers when appropriate
- Group related menu items with sections and dividers
- Test drawer behavior in both LTR and RTL modes
Don'ts
- Don't force drawer positions - Flutter handles RTL automatically
- Don't forget to close drawer after navigation
- Don't overload drawer with too many items
- Don't skip localization for section headers
Conclusion
Drawer is essential for creating navigation menus in multilingual Flutter applications. With automatic RTL support, Drawer opens from the correct side of the screen based on the text direction. Use Drawer with localized headers, grouped sections, and expandable items to create intuitive navigation experiences that work across all languages.