How To Build A Bottom Navigation Bar In Flutter

Master Flutter's bottom navigation widgets to create intuitive, user-friendly app navigation with BottomNavigationBar, NavigationBar, and custom implementations.

Introduction

In modern mobile app development, navigation plays a pivotal role in shaping the user experience. One of the most ubiquitous and user-friendly navigation patterns is the bottom navigation bar--a horizontal bar positioned at the bottom of the screen that provides quick access to primary sections of an application. Flutter, Google's UI toolkit, offers robust support for implementing bottom navigation through multiple approaches, catering to different design requirements and complexity levels.

A bottom navigation bar typically displays three to five destinations, allowing users to quickly switch between the main functional areas of an app. This navigation pattern is particularly effective for mobile applications because it remains easily accessible at the bottom of the screen--within thumb reach on most devices--while providing a clear visual hierarchy of the app's primary sections.

Flutter provides developers with several options for implementing bottom navigation. The framework includes the BottomNavigationBar widget, which has been a staple of Material Design implementations for years. However, with the introduction of Material Design 3, Google has also introduced the NavigationBar widget, which offers enhanced styling capabilities and a more modern aesthetic. Additionally, developers can create completely custom bottom navigation bars using fundamental Flutter widgets like Container, Row, and IconButton, giving them full control over appearance and behavior.

Whether you're building consumer apps or enterprise mobile solutions, understanding these navigation patterns is essential for creating professional applications. Our team specializes in mobile app development services that help businesses implement robust navigation architectures and deliver exceptional user experiences across iOS and Android platforms from a single codebase.

This guide walks you through each approach in detail. For new applications targeting Material Design 3, the NavigationBar widget is the recommended choice. For applications requiring specific visual treatments or enhanced functionality, custom implementations provide the flexibility needed to create unique navigation experiences. Understanding these options enables informed decisions that balance user experience goals with development efficiency.

Understanding Flutter's Bottom Navigation Widgets

BottomNavigationBar: The Traditional Approach

The BottomNavigationBar widget has been the standard solution for bottom navigation in Flutter applications since the framework's early days. It is a material widget that displays a row of navigation items at the bottom of an app, typically used for selecting among a small number of views--usually between three and five destinations, as defined in the Flutter API Documentation. This widget is designed specifically for the Material Design aesthetic and integrates seamlessly with the Scaffold widget.

The BottomNavigationBar widget operates in conjunction with a Scaffold, where it is provided as the bottomNavigationBar argument. The widget manages its own state and appearance, handling the visual representation of selected and unselected items, as well as touch interactions. Each navigation item consists of an icon and optionally a label, with built-in support for showing labels only for selected items or displaying them consistently.

The widget supports two distinct type modes that determine how items are displayed and animated. When the number of items is less than four, it defaults to BottomNavigationBarType.fixed, where all items remain visible and the selected item is highlighted through color changes. When four or more items are present, it uses BottomNavigationBarType.shifting, which animates the background color of the selected item and can include more dramatic visual transitions between selections.

Understanding the distinction between these types is crucial for implementing the desired visual behavior. The fixed type is ideal for applications where users need to see all navigation options simultaneously, while the shifting type provides a more dynamic experience that draws attention to the currently selected destination through background color changes and elevation shifts.

NavigationBar: The Material 3 Modern Alternative

Material Design 3 introduced significant updates to the bottom navigation pattern, and Flutter has responded with the NavigationBar widget as the modern replacement for BottomNavigationBar. The NavigationBar widget provides a cleaner, more sophisticated appearance that aligns with the Material Design 3 design language, featuring subtle elevation changes, improved color handling, and enhanced theming capabilities, as documented in the NavigationBar API.

The NavigationBar widget differs from its predecessor in several key ways. It uses NavigationDestination widgets instead of BottomNavigationBarItem, and it employs the onDestinationSelected callback rather than the traditional onTap handler. The visual presentation is more minimal, with smoother animations and better integration with Material 3 color schemes and theming systems.

One of the most significant advantages of the NavigationBar widget is its enhanced styling flexibility. Developers can more easily customize the appearance to match specific design requirements, including the shape of indicator decorations, the spacing between destinations, and the handling of selected versus unselected states. This makes it particularly suitable for applications that need to implement precise brand guidelines or unique visual identities.

For teams building modern mobile applications, partnering with experienced Flutter developers can accelerate implementation and ensure adherence to Material Design best practices while delivering polished user interfaces.

Implementing BottomNavigationBar

Basic Implementation Structure

Implementing a basic bottom navigation bar in Flutter requires a few essential components working together. At the core, you need a stateful widget that manages the current selection index, a list of page widgets representing each navigation destination, and the BottomNavigationBar widget itself configured with the appropriate items and callbacks.

The implementation begins with creating a StatefulWidget that will serve as the container for the navigation structure. This widget maintains the current selection state and provides the callback function that updates the selected index when users interact with the navigation bar. The state management approach ensures that the UI reflects the current selection and allows for smooth transitions between different sections of the application.

Complete Code Example

import 'package:flutter/material.dart';

void main() {
 runApp(const MyApp());
}

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

 @override
 Widget build(BuildContext context) {
 return MaterialApp(
 title: 'Flutter Bottom Navigation Demo',
 theme: ThemeData(
 primarySwatch: Colors.blue,
 useMaterial3: true,
 ),
 home: const HomeScreen(),
 );
 }
}

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

 @override
 State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
 int _currentIndex = 0;

 final List<Widget> _pages = [
 const HomePage(),
 const SearchPage(),
 const FavoritesPage(),
 const ProfilePage(),
 ];

 void _onItemTapped(int index) {
 setState(() {
 _currentIndex = index;
 });
 }

 @override
 Widget build(BuildContext context) {
 return Scaffold(
 body: _pages[_currentIndex],
 bottomNavigationBar: BottomNavigationBar(
 currentIndex: _currentIndex,
 onTap: _onItemTapped,
 type: BottomNavigationBarType.fixed,
 selectedItemColor: Colors.blue,
 unselectedItemColor: Colors.grey,
 backgroundColor: Colors.white,
 elevation: 8,
 items: const [
 BottomNavigationBarItem(
 icon: Icon(Icons.home_outlined),
 activeIcon: Icon(Icons.home),
 label: 'Home',
 ),
 BottomNavigationBarItem(
 icon: Icon(Icons.search),
 label: 'Search',
 ),
 BottomNavigationBarItem(
 icon: Icon(Icons.favorite_border),
 activeIcon: Icon(Icons.favorite),
 label: 'Favorites',
 ),
 BottomNavigationBarItem(
 icon: Icon(Icons.person_outline),
 activeIcon: Icon(Icons.person),
 label: 'Profile',
 ),
 ],
 ),
 );
 }
}

// Placeholder page widgets
class HomePage extends StatelessWidget {
 const HomePage({super.key});

 @override
 Widget build(BuildContext context) {
 return const Center(child: Text('Home Page'));
 }
}

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

 @override
 Widget build(BuildContext context) {
 return const Center(child: Text('Search Page'));
 }
}

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

 @override
 Widget build(BuildContext context) {
 return const Center(child: Text('Favorites Page'));
 }
}

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

 @override
 Widget build(BuildContext context) {
 return const Center(child: Text('Profile Page'));
 }
}

Customization Options

The BottomNavigationBar widget offers extensive customization options. The selectedItemColor property controls the color of the selected item's icon and label, while unselectedItemColor defines the appearance of non-selected items. The backgroundColor property enables customization of the navigation bar's background, and the elevation property controls the shadow depth for visual separation from the content area.

For applications using more than four items, switching to BottomNavigationBarType.shifting provides animated background color transitions that draw attention to the selected destination. This type is particularly effective when the visual hierarchy of destinations needs to be emphasized through elevation and color changes.

Implementing these patterns correctly is essential for delivering professional-grade mobile applications. Our web development services include mobile app development expertise to help you build robust navigation architectures.

Building with NavigationBar (Material 3)

Transitioning to Material 3

The NavigationBar widget represents Flutter's commitment to supporting the latest Material Design specifications. Implementing this widget requires understanding its API differences from the traditional BottomNavigationBar and adapting your implementation accordingly. The NavigationBar uses a destination-based model that provides more flexibility in defining navigation items.

To migrate from BottomNavigationBar to NavigationBar, several key changes are required. Instead of BottomNavigationBarItem widgets, you use NavigationDestination widgets, which provide a similar structure but with updated naming conventions. The onTap callback is replaced with onDestinationSelected, which follows a more explicit naming pattern that clarifies its purpose in handling navigation events.

Code Example for NavigationBar

import 'package:flutter/material.dart';

void main() {
 runApp(const MyApp());
}

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

 @override
 Widget build(BuildContext context) {
 return MaterialApp(
 title: 'Flutter NavigationBar Demo',
 theme: ThemeData(
 useMaterial3: true,
 colorScheme: ColorScheme.fromSeed(
 seedColor: Colors.blue,
 primary: Colors.blue,
 ),
 ),
 home: const HomeScreen(),
 );
 }
}

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

 @override
 State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
 int _selectedIndex = 0;

 final List<Widget> _pages = [
 const HomePage(),
 const SearchPage(),
 const FavoritesPage(),
 const ProfilePage(),
 ];

 void _onDestinationSelected(int index) {
 setState(() {
 _selectedIndex = index;
 });
 }

 @override
 Widget build(BuildContext context) {
 return Scaffold(
 body: _pages[_selectedIndex],
 bottomNavigationBar: NavigationBar(
 selectedIndex: _selectedIndex,
 onDestinationSelected: _onDestinationSelected,
 indicatorColor: Colors.blue.withOpacity(0.2),
 destinations: const [
 NavigationDestination(
 icon: Icon(Icons.home_outlined),
 selectedIcon: Icon(Icons.home),
 label: 'Home',
 ),
 NavigationDestination(
 icon: Icon(Icons.search_outlined),
 selectedIcon: Icon(Icons.search),
 label: 'Search',
 ),
 NavigationDestination(
 icon: Icon(Icons.favorite_border),
 selectedIcon: Icon(Icons.favorite),
 label: 'Favorites',
 ),
 NavigationDestination(
 icon: Icon(Icons.person_outline),
 selectedIcon: Icon(Icons.person),
 label: 'Profile',
 ),
 ],
 ),
 );
 }
}

// Placeholder page widgets
class HomePage extends StatelessWidget {
 const HomePage({super.key});

 @override
 Widget build(BuildContext context) {
 return const Center(child: Text('Home Page'));
 }
}

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

 @override
 Widget build(BuildContext context) {
 return const Center(child: Text('Search Page'));
 }
}

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

 @override
 Widget build(BuildContext context) {
 return const Center(child: Text('Favorites Page'));
 }
}

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

 @override
 Widget build(BuildContext context) {
 return const Center(child: Text('Profile Page'));
 }
}

The NavigationBar widget accepts several configuration options that control its appearance and behavior. The selectedIndex property determines which destination is currently active, while the onDestinationSelected callback receives the index of the newly selected destination. The indicatorColor and indicatorShape properties allow customization of the selection indicator, and the overall height can be adjusted through the height property.

Creating Custom Bottom Navigation Bars

When to Build Custom Solutions

While Flutter's built-in navigation widgets provide excellent functionality for most use cases, certain situations call for custom bottom navigation implementations. Custom solutions offer complete control over the visual appearance, allowing developers to implement unique designs that may not be achievable with the standard widgets. This approach is particularly valuable when implementing branded interfaces or highly stylized applications that require specific visual treatments, as outlined in guides like the GeeksforGeeks Flutter Custom Bottom Navigation Bar tutorial.

Custom navigation bars also provide opportunities for enhanced functionality that goes beyond what the standard widgets offer. Features such as animated icon transitions, complex badge systems, dynamic content within navigation items, or unusual layout arrangements can be implemented when building from fundamental widgets. The flexibility of Flutter's widget composition model makes it possible to create sophisticated navigation experiences while maintaining the framework's declarative programming paradigm.

Implementation Using Container and Row

Building a custom bottom navigation bar begins with a Container widget that defines the overall dimensions and visual styling of the navigation area. This container can include background colors, border radius values, and shadow effects through its decoration property. Setting an appropriate height ensures the navigation bar has sufficient space for touch targets and visual elements.

import 'package:flutter/material.dart';

class CustomBottomNavigationBar extends StatefulWidget {
 final List<NavigationItem> items;
 final int selectedIndex;
 final ValueChanged<int> onItemSelected;

 const CustomBottomNavigationBar({
 super.key,
 required this.items,
 required this.selectedIndex,
 required this.onItemSelected,
 });

 @override
 State<CustomBottomNavigationBar> createState() => _CustomBottomNavigationBarState();
}

class _CustomBottomNavigationBarState extends State<CustomBottomNavigationBar> {
 @override
 Widget build(BuildContext context) {
 return Container(
 height: 80,
 decoration: BoxDecoration(
 color: Colors.white,
 boxShadow: [
 BoxShadow(
 color: Colors.black.withOpacity(0.1),
 blurRadius: 10,
 offset: const Offset(0, -2),
 ),
 ],
 borderRadius: const BorderRadius.only(
 topLeft: Radius.circular(20),
 topRight: Radius.circular(20),
 ),
 ),
 child: Row(
 mainAxisAlignment: MainAxisAlignment.spaceAround,
 children: widget.items.asMap().entries.map((entry) {
 final index = entry.key;
 final item = entry.value;
 final isSelected = index == widget.selectedIndex;

 return Expanded(
 child: GestureDetector(
 onTap: () => widget.onItemSelected(index),
 child: Column(
 mainAxisAlignment: MainAxisAlignment.center,
 children: [
 Icon(
 isSelected ? item.selectedIcon : item.icon,
 color: isSelected ? Colors.blue : Colors.grey,
 size: 28,
 ),
 const SizedBox(height: 4),
 Text(
 item.label,
 style: TextStyle(
 color: isSelected ? Colors.blue : Colors.grey,
 fontSize: 12,
 fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400,
 ),
 ),
 ],
 ),
 ),
 );
 }).toList(),
 ),
 );
 }
}

class NavigationItem {
 final IconData icon;
 final IconData selectedIcon;
 final String label;

 NavigationItem({
 required this.icon,
 required this.selectedIcon,
 required this.label,
 });
}

Advanced Customization Techniques

Beyond basic customization, developers can implement sophisticated features that enhance the navigation experience. Animated transitions between icon states can be achieved using Flutter's animation framework, creating smooth visual feedback that indicates selection changes. These animations can include scaling effects, rotation, or opacity changes that make the navigation feel more responsive and polished.

Badge indicators are a common requirement for navigation bars, particularly in applications that need to display notification counts or status information. Implementing badges requires layering additional widgets on top of the navigation icons, typically using a Stack widget to overlay the badge content. The badge content can be dynamically updated based on application state, providing real-time feedback to users.

Custom shapes and visual effects can be achieved through the Container's decoration property, including rounded corners, gradients, and custom border treatments. For more complex shapes, developers can use ClipRect or custom clipper implementations to create navigation bars with unique silhouettes.

For organizations requiring specialized mobile applications with custom navigation requirements, our AI automation services can integrate intelligent features that enhance user engagement and streamline complex workflows within mobile interfaces.

State Management and Navigation

IndexedStack for Page Persistence

When implementing bottom navigation, preserving the state of each page is essential for providing a smooth user experience. The IndexedStack widget provides an elegant solution by maintaining all child widgets in the widget tree while only displaying one at a time. This approach ensures that scroll positions, form inputs, and other stateful elements are preserved when users navigate between sections, as recommended in the GeeksforGeeks Flutter navigation guide.

Using IndexedStack requires wrapping the page widgets in the IndexedStack widget and specifying the current index to determine which child is visible. The stack maintains all children in memory, so transitioning back to a previously viewed page displays it with its complete state intact.

IndexedStack(
 index: _currentIndex,
 children: _pages,
)

The IndexedStack approach does have memory implications, as all pages remain instantiated even when not visible. For applications with many pages or memory-intensive content, developers may need to implement lazy loading strategies or use alternative state management approaches. However, for typical applications with three to five navigation destinations, the memory overhead is generally acceptable given the improved user experience.

Handling Navigation Events

Proper handling of navigation events ensures that users can predictably move between sections of the application. The onTap or onDestinationSelected callback receives the index of the tapped item, which should be used to update the current selection state. This update triggers a rebuild that displays the newly selected page while maintaining the navigation bar's visual state.

Advanced navigation handling may include preventing navigation under certain conditions, such as showing confirmation dialogs before leaving unsaved changes or preventing navigation away from critical workflows. These scenarios require more sophisticated state management that can intercept and potentially modify navigation behavior before it occurs.

Integration with Flutter's routing system enables navigation to detail pages that appear above the main navigation structure. When users navigate to a detail page, the bottom navigation bar can remain visible as a persistent element, or it can be hidden to provide a full-screen detail view. This flexibility allows applications to implement various navigation hierarchies while maintaining the core bottom navigation pattern for primary section access.

For complex applications, consider combining bottom navigation with state management solutions like Provider, Riverpod, or Bloc to maintain application state independently of the widget tree. This approach separates navigation state from UI state, making the codebase more maintainable and easier to test. Our mobile development experts can help architect robust state management patterns that scale with your application complexity.

Key Implementation Considerations

Follow these best practices for effective bottom navigation

Item Count Guidelines

Limit to 3-5 destinations for optimal usability and clarity. Follow Material Design recommendations for navigation hierarchy.

Visual Design

Use clear iconography, consistent styling, and proper color contrast. Ensure selected states are visually distinct.

State Persistence

Use IndexedStack or state management solutions like Provider to preserve page states across tab switches.

Accessibility

Ensure 48dp touch targets, proper color contrast, and screen reader support through descriptive labels.

Frequently Asked Questions

Can I use custom icons instead of Material Icons?

Yes, Flutter's navigation widgets support any widget as an icon. You can use Image.asset for image icons, flutter_svg for vector graphics, or custom widgets. Simply replace the Icon widget in the BottomNavigationBarItem or NavigationDestination with your preferred widget type.

What is the difference between BottomNavigationBar and NavigationBar?

NavigationBar is the Material Design 3 implementation with enhanced styling, smoother animations, and better theming. BottomNavigationBar remains for Material Design 2 compatibility. For new applications, NavigationBar is recommended.

How do I keep the bottom navigation bar persistent across page transitions?

Both widgets are designed to remain visible as part of Scaffold. Push routes that don't remove the scaffold from the tree to maintain the navigation bar. Use Navigator.push with routes that include the same scaffold structure.

Can I add badges to navigation items?

Standard widgets don't include badge support. Custom implementations can add badges using Stack widgets positioned above navigation icons. Several community packages also provide pre-built badge solutions.

How do I handle state persistence when switching tabs?

Use IndexedStack to maintain child widgets in memory, preserving scroll positions and form states. Alternatively, implement state management solutions like Provider, Riverpod, or Bloc to maintain state independently.

Conclusion

Implementing bottom navigation in Flutter offers multiple approaches ranging from the standard Material Design widgets to completely custom solutions. The BottomNavigationBar widget provides a battle-tested implementation that integrates seamlessly with the Material Design system, while the NavigationBar widget offers a more modern alternative aligned with Material Design 3 specifications. For applications requiring unique visual treatments, custom implementations provide unlimited flexibility at the cost of additional implementation complexity.

ApproachBest ForComplexity
BottomNavigationBarTraditional Material Design apps, backward compatibilityLow
NavigationBarNew Material Design 3 applicationsLow
Custom ImplementationBranded designs, unique functionality requirementsHigh

The choice between these approaches depends on your specific requirements, including design fidelity, development timeline, and maintenance considerations. Standard widgets offer faster development and more reliable behavior, while custom solutions provide visual differentiation and enhanced functionality. Understanding the trade-offs between these options enables informed decisions that balance user experience goals with development efficiency.

As you implement bottom navigation in your Flutter applications, remember to follow design guidelines for item count, maintain accessibility compliance, and consider state management requirements that affect user experience. Whether you're building a simple app with a few screens or a complex application with extensive navigation needs, these patterns and techniques provide a foundation for creating navigation experiences that serve your users effectively.

Ready to build professional Flutter applications? Our team of Flutter experts can help you implement robust navigation patterns and create exceptional mobile experiences that delight users. Contact our mobile development team to discuss your project requirements.


Sources

  1. Flutter API Documentation - BottomNavigationBar Class
  2. Flutter API Documentation - NavigationBar Class
  3. BaseProgrammer - How to Create a Bottom Navigation Bar in Flutter (2025 Guide)
  4. GeeksforGeeks - Flutter Custom Bottom Navigation Bar
  5. Material Design 3 - Navigation Bar Overview

Ready to Build Professional Flutter Applications?

Our team of Flutter experts can help you implement robust navigation patterns and create exceptional mobile experiences that delight users.