← Back to Blog

Flutter AppBar Localization: Navigation Headers for Multilingual Apps

flutterappbarnavigationmateriallocalizationrtl

Flutter AppBar Localization: Navigation Headers for Multilingual Apps

AppBar is a Flutter Material Design widget that displays a toolbar at the top of the screen. In multilingual applications, AppBar provides a consistent navigation header that adapts to different languages, supporting RTL layouts with properly mirrored leading and trailing actions.

Understanding AppBar in Localization Context

AppBar displays a title, leading icon, and action buttons in a toolbar format. For multilingual apps, this enables:

  • Direction-aware icon positioning for RTL languages
  • Localized titles that adapt to text length
  • Properly mirrored navigation icons
  • Theme-aware styling across all locales

Why AppBar Matters for Multilingual Apps

AppBar provides:

  • RTL mirroring: Back arrows and icons mirror automatically
  • Flexible titles: Titles adapt to translated text lengths
  • Action alignment: Actions position correctly in both directions
  • Consistent branding: Same visual identity across languages

Basic AppBar Implementation

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class LocalizedAppBarExample extends StatelessWidget {
  const LocalizedAppBarExample({super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.pageTitle),
        actions: [
          IconButton(
            icon: const Icon(Icons.search),
            tooltip: l10n.searchTooltip,
            onPressed: () {},
          ),
          IconButton(
            icon: const Icon(Icons.notifications),
            tooltip: l10n.notificationsTooltip,
            onPressed: () {},
          ),
          PopupMenuButton<String>(
            tooltip: l10n.moreOptions,
            onSelected: (value) {},
            itemBuilder: (context) => [
              PopupMenuItem(
                value: 'settings',
                child: Text(l10n.menuSettings),
              ),
              PopupMenuItem(
                value: 'help',
                child: Text(l10n.menuHelp),
              ),
              PopupMenuItem(
                value: 'about',
                child: Text(l10n.menuAbout),
              ),
            ],
          ),
        ],
      ),
      body: Center(
        child: Text(l10n.bodyContent),
      ),
    );
  }
}

AppBar Variants

Large AppBar with Flexible Space

class LocalizedLargeAppBar extends StatelessWidget {
  const LocalizedLargeAppBar({super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverAppBar.large(
            title: Text(l10n.largeTitle),
            actions: [
              IconButton(
                icon: const Icon(Icons.search),
                tooltip: l10n.searchTooltip,
                onPressed: () {},
              ),
            ],
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) {
                return ListTile(
                  title: Text('${l10n.listItem} ${index + 1}'),
                );
              },
              childCount: 30,
            ),
          ),
        ],
      ),
    );
  }
}

Collapsing AppBar with Image

class LocalizedCollapsingAppBar extends StatelessWidget {
  const LocalizedCollapsingAppBar({super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            expandedHeight: 200,
            pinned: true,
            flexibleSpace: FlexibleSpaceBar(
              title: Text(l10n.pageTitle),
              background: Stack(
                fit: StackFit.expand,
                children: [
                  Image.network(
                    'https://picsum.photos/800/400',
                    fit: BoxFit.cover,
                  ),
                  Container(
                    decoration: BoxDecoration(
                      gradient: LinearGradient(
                        begin: Alignment.topCenter,
                        end: Alignment.bottomCenter,
                        colors: [
                          Colors.transparent,
                          Colors.black.withOpacity(0.7),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ),
            actions: [
              IconButton(
                icon: const Icon(Icons.share),
                tooltip: l10n.shareTooltip,
                onPressed: () {},
              ),
            ],
          ),
          SliverPadding(
            padding: const EdgeInsets.all(16),
            sliver: SliverList(
              delegate: SliverChildListDelegate([
                Text(
                  l10n.sectionTitle,
                  style: Theme.of(context).textTheme.headlineSmall,
                ),
                const SizedBox(height: 8),
                Text(l10n.sectionContent),
              ]),
            ),
          ),
        ],
      ),
    );
  }
}

Search AppBar

Localized Search Bar

class LocalizedSearchAppBar extends StatefulWidget {
  const LocalizedSearchAppBar({super.key});

  @override
  State<LocalizedSearchAppBar> createState() => _LocalizedSearchAppBarState();
}

class _LocalizedSearchAppBarState extends State<LocalizedSearchAppBar> {
  bool _isSearching = false;
  final _searchController = TextEditingController();

  @override
  void dispose() {
    _searchController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(
        title: _isSearching
            ? TextField(
                controller: _searchController,
                autofocus: true,
                decoration: InputDecoration(
                  hintText: l10n.searchHint,
                  border: InputBorder.none,
                ),
                style: const TextStyle(fontSize: 18),
              )
            : Text(l10n.appTitle),
        actions: [
          if (_isSearching)
            IconButton(
              icon: const Icon(Icons.clear),
              tooltip: l10n.clearSearch,
              onPressed: () {
                _searchController.clear();
              },
            )
          else
            IconButton(
              icon: const Icon(Icons.search),
              tooltip: l10n.searchTooltip,
              onPressed: () {
                setState(() => _isSearching = true);
              },
            ),
        ],
        leading: _isSearching
            ? IconButton(
                icon: const Icon(Icons.arrow_back),
                tooltip: l10n.backTooltip,
                onPressed: () {
                  setState(() {
                    _isSearching = false;
                    _searchController.clear();
                  });
                },
              )
            : null,
      ),
      body: Center(
        child: _isSearching
            ? Text(l10n.searchResults)
            : Text(l10n.regularContent),
      ),
    );
  }
}

Tabbed AppBar

AppBar with Tabs

class LocalizedTabbedAppBar extends StatelessWidget {
  const LocalizedTabbedAppBar({super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: Text(l10n.appTitle),
          bottom: TabBar(
            tabs: [
              Tab(
                icon: const Icon(Icons.article),
                text: l10n.tabArticles,
              ),
              Tab(
                icon: const Icon(Icons.photo),
                text: l10n.tabPhotos,
              ),
              Tab(
                icon: const Icon(Icons.video_library),
                text: l10n.tabVideos,
              ),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            Center(child: Text(l10n.articlesContent)),
            Center(child: Text(l10n.photosContent)),
            Center(child: Text(l10n.videosContent)),
          ],
        ),
      ),
    );
  }
}

Contextual AppBar

Selection Mode AppBar

class LocalizedContextualAppBar extends StatefulWidget {
  const LocalizedContextualAppBar({super.key});

  @override
  State<LocalizedContextualAppBar> createState() =>
      _LocalizedContextualAppBarState();
}

class _LocalizedContextualAppBarState
    extends State<LocalizedContextualAppBar> {
  final Set<int> _selectedItems = {};

  bool get _isSelectionMode => _selectedItems.isNotEmpty;

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(
        leading: _isSelectionMode
            ? IconButton(
                icon: const Icon(Icons.close),
                tooltip: l10n.cancelSelection,
                onPressed: () {
                  setState(() => _selectedItems.clear());
                },
              )
            : null,
        title: _isSelectionMode
            ? Text(l10n.selectedCount(_selectedItems.length))
            : Text(l10n.appTitle),
        backgroundColor: _isSelectionMode
            ? Theme.of(context).colorScheme.primaryContainer
            : null,
        actions: _isSelectionMode
            ? [
                IconButton(
                  icon: const Icon(Icons.share),
                  tooltip: l10n.shareSelected,
                  onPressed: () {},
                ),
                IconButton(
                  icon: const Icon(Icons.delete),
                  tooltip: l10n.deleteSelected,
                  onPressed: () {
                    setState(() => _selectedItems.clear());
                  },
                ),
              ]
            : [
                IconButton(
                  icon: const Icon(Icons.search),
                  tooltip: l10n.searchTooltip,
                  onPressed: () {},
                ),
              ],
      ),
      body: ListView.builder(
        itemCount: 20,
        itemBuilder: (context, index) {
          final isSelected = _selectedItems.contains(index);
          return ListTile(
            leading: _isSelectionMode
                ? Checkbox(
                    value: isSelected,
                    onChanged: (value) {
                      setState(() {
                        if (value == true) {
                          _selectedItems.add(index);
                        } else {
                          _selectedItems.remove(index);
                        }
                      });
                    },
                  )
                : CircleAvatar(child: Text('${index + 1}')),
            title: Text('${l10n.itemTitle} ${index + 1}'),
            selected: isSelected,
            onTap: () {
              if (_isSelectionMode) {
                setState(() {
                  if (isSelected) {
                    _selectedItems.remove(index);
                  } else {
                    _selectedItems.add(index);
                  }
                });
              }
            },
            onLongPress: () {
              setState(() => _selectedItems.add(index));
            },
          );
        },
      ),
    );
  }
}

Custom AppBar Styling

Themed AppBar

class LocalizedThemedAppBar extends StatelessWidget {
  const LocalizedThemedAppBar({super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.appTitle),
        centerTitle: true,
        elevation: 0,
        backgroundColor: Theme.of(context).colorScheme.surface,
        foregroundColor: Theme.of(context).colorScheme.onSurface,
        shape: const RoundedRectangleBorder(
          borderRadius: BorderRadius.vertical(
            bottom: Radius.circular(16),
          ),
        ),
        actions: [
          Container(
            margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
            child: IconButton.filledTonal(
              icon: const Icon(Icons.notifications_outlined),
              tooltip: l10n.notificationsTooltip,
              onPressed: () {},
            ),
          ),
          Container(
            margin: const EdgeInsets.only(right: 8, top: 8, bottom: 8),
            child: IconButton.filledTonal(
              icon: const Icon(Icons.person_outline),
              tooltip: l10n.profileTooltip,
              onPressed: () {},
            ),
          ),
        ],
      ),
      body: Center(
        child: Text(l10n.bodyContent),
      ),
    );
  }
}

Transparent AppBar with Gradient

class LocalizedTransparentAppBar extends StatelessWidget {
  const LocalizedTransparentAppBar({super.key});

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        elevation: 0,
        title: Text(l10n.appTitle),
        flexibleSpace: Container(
          decoration: BoxDecoration(
            gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: [
                Colors.black.withOpacity(0.7),
                Colors.transparent,
              ],
            ),
          ),
        ),
      ),
      body: Container(
        decoration: const BoxDecoration(
          image: DecorationImage(
            image: NetworkImage('https://picsum.photos/800/1200'),
            fit: BoxFit.cover,
          ),
        ),
        child: SafeArea(
          child: Center(
            child: Text(
              l10n.overlayContent,
              style: const TextStyle(
                color: Colors.white,
                fontSize: 24,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

ARB File Structure

English (app_en.arb)

{
  "@@locale": "en",

  "appTitle": "My App",
  "pageTitle": "Page Title",
  "searchTooltip": "Search",
  "notificationsTooltip": "Notifications",
  "moreOptions": "More options",
  "menuSettings": "Settings",
  "menuHelp": "Help",
  "menuAbout": "About",
  "bodyContent": "Main content area",

  "largeTitle": "Large Title",
  "listItem": "Item",
  "shareTooltip": "Share",
  "sectionTitle": "Section Title",
  "sectionContent": "This is the content of the section.",

  "searchHint": "Search...",
  "clearSearch": "Clear",
  "backTooltip": "Back",
  "searchResults": "Search results will appear here",
  "regularContent": "Regular content",

  "tabArticles": "Articles",
  "tabPhotos": "Photos",
  "tabVideos": "Videos",
  "articlesContent": "Articles content",
  "photosContent": "Photos content",
  "videosContent": "Videos content",

  "cancelSelection": "Cancel selection",
  "selectedCount": "{count} selected",
  "@selectedCount": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },
  "shareSelected": "Share selected",
  "deleteSelected": "Delete selected",
  "itemTitle": "Item",

  "profileTooltip": "Profile",
  "overlayContent": "Overlay Content"
}

German (app_de.arb)

{
  "@@locale": "de",

  "appTitle": "Meine App",
  "pageTitle": "Seitentitel",
  "searchTooltip": "Suchen",
  "notificationsTooltip": "Benachrichtigungen",
  "moreOptions": "Weitere Optionen",
  "menuSettings": "Einstellungen",
  "menuHelp": "Hilfe",
  "menuAbout": "Über",
  "bodyContent": "Hauptinhaltsbereich",

  "largeTitle": "Großer Titel",
  "listItem": "Element",
  "shareTooltip": "Teilen",
  "sectionTitle": "Abschnittstitel",
  "sectionContent": "Dies ist der Inhalt des Abschnitts.",

  "searchHint": "Suchen...",
  "clearSearch": "Löschen",
  "backTooltip": "Zurück",
  "searchResults": "Suchergebnisse erscheinen hier",
  "regularContent": "Regulärer Inhalt",

  "tabArticles": "Artikel",
  "tabPhotos": "Fotos",
  "tabVideos": "Videos",
  "articlesContent": "Artikelinhalt",
  "photosContent": "Fotoinhalt",
  "videosContent": "Videoinhalt",

  "cancelSelection": "Auswahl abbrechen",
  "selectedCount": "{count} ausgewählt",
  "shareSelected": "Ausgewählte teilen",
  "deleteSelected": "Ausgewählte löschen",
  "itemTitle": "Element",

  "profileTooltip": "Profil",
  "overlayContent": "Overlay-Inhalt"
}

Arabic (app_ar.arb)

{
  "@@locale": "ar",

  "appTitle": "تطبيقي",
  "pageTitle": "عنوان الصفحة",
  "searchTooltip": "بحث",
  "notificationsTooltip": "الإشعارات",
  "moreOptions": "المزيد من الخيارات",
  "menuSettings": "الإعدادات",
  "menuHelp": "المساعدة",
  "menuAbout": "حول",
  "bodyContent": "منطقة المحتوى الرئيسية",

  "largeTitle": "عنوان كبير",
  "listItem": "عنصر",
  "shareTooltip": "مشاركة",
  "sectionTitle": "عنوان القسم",
  "sectionContent": "هذا هو محتوى القسم.",

  "searchHint": "بحث...",
  "clearSearch": "مسح",
  "backTooltip": "رجوع",
  "searchResults": "ستظهر نتائج البحث هنا",
  "regularContent": "المحتوى العادي",

  "tabArticles": "المقالات",
  "tabPhotos": "الصور",
  "tabVideos": "الفيديوهات",
  "articlesContent": "محتوى المقالات",
  "photosContent": "محتوى الصور",
  "videosContent": "محتوى الفيديوهات",

  "cancelSelection": "إلغاء التحديد",
  "selectedCount": "{count} محدد",
  "shareSelected": "مشاركة المحدد",
  "deleteSelected": "حذف المحدد",
  "itemTitle": "عنصر",

  "profileTooltip": "الملف الشخصي",
  "overlayContent": "محتوى التراكب"
}

Best Practices Summary

Do's

  1. Let AppBar handle RTL icon mirroring automatically
  2. Use tooltips for all icon buttons
  3. Localize menu items in PopupMenuButton
  4. Test title overflow with long translations
  5. Use SliverAppBar for scrollable headers

Don'ts

  1. Don't hardcode back arrow icons - use automaticallyImplyLeading
  2. Don't forget to localize search hints and placeholders
  3. Don't use fixed widths for title widgets
  4. Don't skip contextual app bars for selection modes

Conclusion

AppBar is essential for creating consistent navigation headers in multilingual Flutter applications. With automatic RTL support and flexible title and action positioning, AppBar ensures your navigation works correctly across all languages. Use AppBar variants like SliverAppBar and custom styling to create engaging, localized header experiences.

Further Reading