Migration to Flutter Guide
Discover our battle-tested 21-step framework for a smooth and successful migration to Flutter!
Home
Glossary

Bottom navigation bar in Flutter

What is bottom navigation bar in Flutter?

A bottom navigation bar in Flutter is a persistent row of icons anchored to the screen's bottom edge that lets users switch between an app's top-level destinations without leaving the current screen.

Flutter ships two official widgets for it – NavigationBar (Material 3) and BottomNavigationBar (Material 2) – plus packages for curved, floating, or animated variants. It differs from TabBar, which switches content within a page, not between an app's destinations.

Key facts at a glance

  • Bottom Navigation Bar in Flutter is a Material navigation widget.
  • Flutter ships two built-in versions: NavigationBar for Material 3 and BottomNavigationBar for Material 2.
  • It sits in the Scaffold.bottomNavigationBar slot.
  • Packages like curved_navigation_bar and stylish_bottom_bar add curved, floating, or animated styles.
  • Material guidance recommends 3–5 destinations.
  • It's most commonly used for app-level tab switching in e-commerce and social apps.

How does the bottom navigation bar work in Flutter?

Bottom Navigation Bar in Flutter sits inside Scaffold's bottomNavigationBar slot, alongside appBar, body, and floatingActionButton. Selecting a destination doesn't push a new route – onDestinationSelected (NavigationBar) or onTap (BottomNavigationBar) fires with an index, and the parent widget swaps body to match, typically via IndexedStack so every tab keeps its own scroll position and state.

This is why bottom navigation bars pair badly with Navigator.push for tab switching: pushing a route replaces the whole Scaffold, bar included, instead of just the body.

NavigationBar renders Material 3's pill-shaped indicator behind the active icon; BottomNavigationBar renders a colored icon and label with no indicator shape. NavigationBar's appearance – indicator shape, colors, height – is driven by ColorScheme and Theme.useMaterial3, which defaults to true since Flutter 3.16; apps still on Material 2 theming will see a different look.

How do I add a bottom navigation bar in Flutter?

NavigationBar is the Material 3 default and the widget to reach for first. This example switches between two screens using IndexedStack to preserve each tab's state.

import 'package:flutter/material.dart';

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

  @override
  State<HomeShell> createState() => _HomeShellState();
}

class _HomeShellState extends State<HomeShell> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _index,
        children: const [HomeScreen(), ProfileScreen()],
      ),
      bottomNavigationBar: NavigationBar(
        selectedIndex: _index,
        onDestinationSelected: (i) => setState(() => _index = i),
        destinations: const [
          NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
          NavigationDestination(icon: Icon(Icons.person), label: 'Profile'),
        ],
      ),
    );
  }
}

selectedIndex and IndexedStack's index stay in sync, so switching tabs never rebuilds the other screens from scratch.

How do I create a curved or animated bottom navigation bar in Flutter?

Packages like curved_navigation_bar replace the whole bar with a custom-painted, animated shape – useful when "curved" or "floating" is a real design requirement, not achievable with NavigationBar styling alone.

import 'package:flutter/material.dart';
import 'package:curved_navigation_bar/curved_navigation_bar.dart';

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

  @override
  State<CurvedHomeShell> createState() => _CurvedHomeShellState();
}

class _CurvedHomeShellState extends State<CurvedHomeShell> {
  int _page = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: CurvedNavigationBar(
        index: _page,
        backgroundColor: Colors.blueAccent,
        items: const [
          Icon(Icons.home, size: 30),
          Icon(Icons.search, size: 30),
          Icon(Icons.person, size: 30),
        ],
        onTap: (i) => setState(() => _page = i),
      ),
      body: Container(color: Colors.blueAccent),
    );
  }
}

The animation curve and duration are the package's, not Flutter's – animationCurve and animationDuration parameters control the shape's motion, separate from NavigationBar's own transition system.

When should I use a bottom navigation bar in Flutter – and when not?

Use it when

  • You have 3–5 top-level, equally important destinations (Home, Search, Profile).
  • Users need to jump between sections without a back stack building up.
  • The design needs a floating or notched look – pair BottomAppBar with CircularNotchedRectangle and a docked FAB.

Avoid it when

  • You have more than 5 destinations – use a Drawer instead.
  • Destinations are sub-sections of one page – use TabBar with TabBarView instead.
  • Screens need a real back stack (checkout, wizards) – bottom nav assumes flat, parallel destinations.
  • You are targeting a distinctly iOS-style UI — NavigationBar and BottomNavigationBar are Material widgets; use CupertinoTabBar instead for an iOS-styled bottom tab bar.

NavigationBar (M3)BottomNavigationBar (M2)
Spec
Current M3 defaultM2, still supported
Selection style
Pill indicatorColored icon + label
Item type
NavigationDestinationBottomNavigationBarItem
Type modes
None, all items shownfixed or shifting
Callback
onDestinationSelectedonTap
Best for
New M3 appsLegacy code, custom-styled bars

Verdict: default to NavigationBar for new M3 screens; keep BottomNavigationBar only where an app already relies on its shifting type or custom theming.

Best practices

  • Drive body with IndexedStack, not Navigator – state and scroll position survive tab switches.
  • Cap destinations at 5 – Material guidance treats this as the practical ceiling before a bar feels cramped.
  • Give every destination a label, even with labelBehavior: onlyShowSelectedNavigationDestination.tooltip defaults to the label text and stays available to screen readers (as long-press tooltip and accessibility label) regardless of whether labelBehavior hides the label visually.
  • Use NavigationBar for new Material 3 screens – it's the current default, not BottomNavigationBar.
  • Reach for a package only when the design genuinely needs curves, floating shapes, or bespoke motionNavigationBar's indicatorColor and labelTextStyle cover most brand theming without one.

Common mistakes

  • Rebuilding the body with Navigator.push on tab tap. This replaces the whole Scaffold, hiding the bar; swap body via IndexedStack or a similar index-driven widget instead.
  • Defaulting to BottomNavigationBar in a Material 3 app. NavigationBar is the current spec's widget; BottomNavigationBar is Material 2 and won't pick up M3 theming automatically.
  • Not setting type explicitly on BottomNavigationBar with 4+ items. It silently switches to shifting, which resizes and recolors items in a way that surprises most first-time users.
  • Hiding labels for a "cleaner" look without testing with a screen reader. NavigationDestination.label must still be provided even when labelBehaviour hides the label visually. NavigationDestination.tooltip defaults to the label text, so you normally do not need to set it separately. If you intentionally need different tooltip text, it can be overridden.

Performance & accessibility notes

  • IndexedStack keeps every tab's widget alive off-screen, preserving scroll position and form state – the tradeoff is that all tabs are built and kept in memory, so keep off-screen tabs lightweight or lazy-load their data.
  • NavigationDestination (Material 3) and BottomNavigationBarItem (Material 2) labels remain available to accessibility services even when hidden visually, so screen readers can still announce them. For NavigationDestination, labelBehavior controls visual label visibility; BottomNavigationBar uses showSelectedLabels and showUnselectedLabels instead. Both components require a label.

Learn more

Get Ready for Edge-to-Edge: Designing Flutter Apps

Mastering Edge-To-Edge in Flutter: A Deep Dive Into the System Navigation Bar in Android

Starting with Android 15, edge-to-edge becomes the default - bringing a modern, immersive feel to apps. In this article, our Flutter developer shows how to handle system bars in Flutter across Android versions and prepare your UI for Android 16, where edge-to-edge will be mandatory.

Complex Animations in Flutter

Complex Animations in Flutter

Flutter ships with plenty of high-quality widgets, layouts, and themes that developers can use to speed up the whole creation process. A great example of custom widgets made in Flutter is the Placement Wheel developed for one of our clients. See how to do it.