← Back to Blog

Flutter Drawer Localization: Navigation Menus for Multilingual Apps

flutterdrawernavigationmateriallocalizationrtl

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

  1. Let Flutter handle RTL drawer positioning automatically
  2. Use EdgeInsetsDirectional for directional padding in sub-items
  3. Include user account headers when appropriate
  4. Group related menu items with sections and dividers
  5. Test drawer behavior in both LTR and RTL modes

Don'ts

  1. Don't force drawer positions - Flutter handles RTL automatically
  2. Don't forget to close drawer after navigation
  3. Don't overload drawer with too many items
  4. 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.

Further Reading