Flutter Bottom Navigation Bar: Complete Beginner’s Guide (2026)

Almost every production Flutter app has a bottom navigation bar. It is how users jump between top-level sections — Home, Search, Profile — without losing their place in any of them. It looks simple, but beginners hit the same bugs every time: tabs not switching, the selected highlight not updating, the bar disappearing after a navigation push, or scroll position resetting on every tab change.

This guide builds a complete bottom navigation bar step by step, starting from the minimal three-screen version and progressing to screen state persistence, the updated Material 3 NavigationBar widget, notification badges, and a properly structured multi-screen app shell. Every common mistake has a working fix.

📋 Prerequisites

1. How BottomNavigationBar Works

Flutter’s BottomNavigationBar is a Material widget placed in Scaffold.bottomNavigationBar. It shows a row of labelled icon tabs at the bottom of the screen. The mental model has three moving parts:

Part What it is Your responsibility
currentIndexInteger that tells the bar which tab is highlightedStore it in state, pass it to the bar
onTapCallback fired with the tapped tab’s indexCall setState() to update currentIndex
bodyThe main content area of the ScaffoldShow _screens[_selectedIndex]
💡 Fixed vs Shifting type
With 3 items, the bar defaults to BottomNavigationBarType.fixed — all tabs are always visible with equal width and labels always shown. With 4 or more items, it defaults to BottomNavigationBarType.shifting — unselected tabs show smaller with no label. You can override the type explicitly if you want different behaviour.

2. The Minimal 3-Screen Example

Here is the smallest possible working bottom navigation bar. Read the comments — every line has a reason:

3. Full Example with Titles, Colors, and Custom Icons

The minimal version works, but production apps need a few more things: a per-tab AppBar title, custom selected/unselected colours, and a slightly more polished look. Here is the complete version:

4. Keeping Screen State Alive with IndexedStack

The examples above swap screens by showing _screens[_selectedIndex] — this works, but it destroys and recreates each screen every time you switch tabs. That means scroll position resets, form data is lost, and any async data fetching repeats.

The fix is IndexedStack. It keeps all screens mounted in the widget tree simultaneously but only makes the selected one visible. Scroll position, state, and loaded data all survive tab switches:

Here is a concrete demonstration — the counter on the Home tab survives switching away and back because IndexedStack keeps HomeScreen alive:

⚠️ Warning: IndexedStack has a memory trade-off
Because all screens stay mounted simultaneously, they all consume memory at once. For simple apps this is fine. For screens that load large images or data sets, consider using AutomaticKeepAliveClientMixin on individual screens that need persistence, rather than keeping everything alive.

5. Material 3: NavigationBar (The Modern Way)

Flutter’s docs say NavigationBar is the updated Material 3 replacement for BottomNavigationBar and is the preferred choice for new apps. It has a more modern pill-shaped indicator, cleaner typography, and better integration with ColorScheme. The API is very similar:

Property BottomNavigationBar NavigationBar (M3)
Selected tabcurrentIndexselectedIndex
Tap handleronTaponDestinationSelected
Tab itemsBottomNavigationBarItemNavigationDestination
Selection indicatorColoured label + iconPill-shaped background
Material versionMaterial 2Material 3 ✅ preferred
💡 Tip: Use NavigationBar for new projects
If you are starting a new Flutter project in 2026, use NavigationBar with useMaterial3: true in your ThemeData. It is the current recommended approach. Only use the older BottomNavigationBar if you are maintaining a legacy app that has not migrated to Material 3 yet.

6. Adding Notification Badges

Notification badges — the red dot or number on a tab — are common in real apps. With NavigationBar, wrap the icon in a Badge widget. With BottomNavigationBar, wrap it in a Stack manually:

7. Dynamic AppBar Title per Tab

Most real apps update the AppBar title when the active tab changes. The cleanest way is to derive the title from a list keyed to the tab index, so there is a single source of truth:

8. Common Beginner Mistakes

Mistake 1: Not calling setState — bar highlights but screen doesn’t change (or vice versa)

Mistake 2: _selectedIndex lives in the wrong widget — bar and screen are out of sync

Mistake 3: Bar disappears after Navigator.push

Mistake 4: Screens order in the list doesn’t match items order in the bar

Mistake 5: Confusing BottomNavigationBar and NavigationBar property names

9. Interview Q&A

Q: What is BottomNavigationBar in Flutter?

A: It is a Material widget placed in Scaffold.bottomNavigationBar that shows a row of icon-and-label tabs at the bottom of the screen. It is used for switching between a small number of top-level views — typically 3 to 5. The selected tab is controlled with currentIndex and taps are handled with onTap.
Q: What do currentIndex and onTap do?

A: currentIndex tells the bar which tab to highlight — you pass your state variable here. onTap is a callback that fires when the user taps a tab, receiving the tapped index. Inside onTap you call setState() to update currentIndex, which causes both the bar highlight and the body content to update together.
Q: Why is my bottom navigation bar not switching screens?

A: The two most common causes are: (1) you are updating _selectedIndex but not inside a setState() call — Flutter never rebuilds, so the screen never changes; or (2) the _selectedIndex variable, the body, and the BottomNavigationBar live in different widgets — setState in one widget only rebuilds that widget, not the others. All three must live in the same StatefulWidget.
Q: Why does the bar disappear when I push a new route?

A: Because Navigator.push() places a completely new page on top of the current one — including on top of the Scaffold that contains the bar. The new page has no bar of its own. This is actually correct behaviour for a detail page (users expect to go back). If you need the bar to stay visible, keep that screen as a tab body and swap it in instead of pushing a new route.
Q: How do I keep screen state when switching tabs?

A: Use IndexedStack as the body instead of _screens[_selectedIndex]. IndexedStack keeps all its children mounted in the widget tree simultaneously and only shows the one at the selected index. Scroll position, form data, and loaded state all survive tab switches.
Q: Is there a newer widget than BottomNavigationBar?

A: Yes — NavigationBar is the Material 3 replacement and is the recommended choice for new projects. It uses selectedIndex and onDestinationSelected instead of currentIndex and onTap, and its items are NavigationDestination widgets instead of BottomNavigationBarItem. Enable it by setting useMaterial3: true in your ThemeData.
Post Why it’s relevant
Flutter Widgets Explained: Stateless vs StatefulThe navigation bar depends entirely on StatefulWidget and setState() — this is the prerequisite to understand first.
Flutter Navigation Explained: Push, Pop, and Named RoutesExplains what happens when you call Navigator.push() — essential for understanding why the nav bar disappears on pushed routes.
Flutter Layout Made Easy: Row, Column, Flex and ExpandedUnderstanding layout fundamentals makes it easier to build the screen widgets that sit inside each tab.
Building a Notes App – Part 1A real Flutter project that puts Scaffold, AppBar, and list widgets together — the same structure used for each tab screen here.
Flutter Search Bar from ScratchThe Search tab in this guide is a perfect starting point — add a real-time filtering search bar to complete it.
Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply