Creating a Bottom Navigation Bar with a Floating Action Button in Flutter

08 Dec 2025

9K

35K

Creating a Bottom Navigation Bar with a Floating Action Button in Flutter

Introduction

In modern mobile application design, a Bottom Navigation Bar (BNB) and a Floating Action Button (FAB) are crucial UI elements that enhance user experience. A Bottom Navigation Bar provides quick access to top-level destinations in an app, while a Floating Action Button represents the primary action on a screen. Combining these two elements effectively, often by integrating the FAB into the BNB with a distinct notch, creates an intuitive and visually appealing interface. This article will guide you through the process of implementing such a design in Flutter.

Prerequisites

  • Basic understanding of Flutter and Dart.
  • Flutter SDK installed and configured.
  • An IDE like VS Code or Android Studio with Flutter and Dart plugins.

Step-by-Step Implementation

1. Set Up a New Flutter Project

First, create a new Flutter project if you don't have one. You can do this via your terminal:


flutter create bottom_nav_fab_example
cd bottom_nav_fab_example

Open the project in your IDE and clear the default boilerplate code in lib/main.dart, leaving just a basic MaterialApp.


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: 'Bottom Nav with FAB',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;

  static const List<Widget> _widgetOptions = <Widget>[
    Text(
      'Home Page',
      style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
    ),
    Text(
      'Search Page',
      style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
    ),
    Text(
      'Profile Page',
      style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
    ),
  ];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Bottom Nav with FAB'),
      ),
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      // Bottom Navigation Bar and FAB will go here
    );
  }
}

2. Implement a Basic Bottom Navigation Bar

Within the Scaffold of your _MyHomePageState, add the bottomNavigationBar property. For now, we'll use a standard BottomNavigationBar.


// Inside _MyHomePageState's build method, within Scaffold:

// ... body ...

bottomNavigationBar: BottomNavigationBar(
  items: const <BottomNavigationBarItem>[
    BottomNavigationBarItem(
      icon: Icon(Icons.home),
      label: 'Home',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.search),
      label: 'Search',
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.person),
      label: 'Profile',
    ),
  ],
  currentIndex: _selectedIndex,
  selectedItemColor: Colors.blueAccent,
  onTap: _onItemTapped,
),

3. Add a Floating Action Button

Next, add a basic FloatingActionButton to your Scaffold.


// Inside _MyHomePageState's build method, within Scaffold:

// ... body ...
// ... bottomNavigationBar ...

floatingActionButton: FloatingActionButton(
  onPressed: () {
    // Handle FAB press
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('FAB pressed!')),
    );
  },
  child: const Icon(Icons.add),
),

At this point, the FAB will appear in the bottom-right corner, potentially overlapping with the bottom navigation items.

4. Integrate FAB with Bottom Navigation Bar (Centering and Notch)

To center the FAB and create a notch in the bottom navigation bar, we need to make a few changes:

  • Set floatingActionButtonLocation in the Scaffold to FloatingActionButtonLocation.centerDocked.
  • Replace the direct BottomNavigationBar with a BottomAppBar, which allows for custom shaping and child placement.
  • The BottomAppBar will have a shape of CircularNotchedRectangle() to create the desired notch.
  • Inside the BottomAppBar, we'll place our BottomNavigationBar items, adjusting their layout to accommodate the centered FAB.

Modify the Scaffold's floatingActionButton and bottomNavigationBar properties as follows:


// Inside _MyHomePageState's build method, within Scaffold:

// ... body ...

floatingActionButton: FloatingActionButton(
  onPressed: () {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('FAB pressed!')),
    );
  },
  shape: const CircleBorder(), // Use CircleBorder for a perfectly round FAB
  child: const Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
  shape: const CircularNotchedRectangle(), // Creates the notch
  notchMargin: 6.0, // Distance between FAB and BottomAppBar
  color: Colors.blue, // Background color of the BottomAppBar
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceAround,
    children: <Widget>[
      IconButton(
        icon: Icon(Icons.home, color: _selectedIndex == 0 ? Colors.white : Colors.blue.shade100),
        onPressed: () => _onItemTapped(0),
        tooltip: 'Home',
      ),
      IconButton(
        icon: Icon(Icons.search, color: _selectedIndex == 1 ? Colors.white : Colors.blue.shade100),
        onPressed: () => _onItemTapped(1),
        tooltip: 'Search',
      ),
      // Spacer for the FAB, if you want only two items on each side
      const SizedBox(width: 48.0), // The width of the FAB itself roughly
      IconButton(
        icon: Icon(Icons.person, color: _selectedIndex == 2 ? Colors.white : Colors.blue.shade100),
        onPressed: () => _onItemTapped(2),
        tooltip: 'Profile',
      ),
      // Add more items here if needed, like a "Settings" button
      // IconButton(
      //   icon: Icon(Icons.settings, color: _selectedIndex == 3 ? Colors.white : Colors.blue.shade100),
      //   onPressed: () => _onItemTapped(3),
      //   tooltip: 'Settings',
      // ),
    ],
  ),
),

Notice that we manually created IconButton widgets inside the Row within BottomAppBar. This gives us finer control over their placement relative to the FAB notch. We also added a SizedBox as a placeholder to push items apart and make space for the FAB. The icons' colors are updated based on _selectedIndex for visual feedback.

If you have more than two navigation items, you might want to adjust the number of items on each side of the FAB or use a different layout within the BottomAppBar's child. For instance, if you have 4 items, you could place 2 on the left and 2 on the right of the FAB.

5. Refining the Page Content (Optional)

For a better demonstration, let's create actual pages instead of just Text widgets.


// Create new files like home_page.dart, search_page.dart, profile_page.dart
// For example, in lib/home_page.dart:
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text(
        'Home Page Content',
        style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.blueAccent),
      ),
    );
  }
}

// Similarly for SearchPage and ProfilePage.

Then, update your _widgetOptions list in main.dart:


import 'package:bottom_nav_fab_example/home_page.dart';
import 'package:bottom_nav_fab_example/search_page.dart';
import 'package:bottom_nav_fab_example/profile_page.dart';

// ...

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;

  static const List<Widget> _widgetOptions = <Widget>[
    HomePage(),
    SearchPage(),
    ProfilePage(),
  ];

  // ...
}

Full Code Example (main.dart)

Here's the complete main.dart file incorporating all the steps:


import 'package:flutter/material.dart';

// Assuming you have these files in your lib directory
// import 'package:bottom_nav_fab_example/home_page.dart';
// import 'package:bottom_nav_fab_example/search_page.dart';
// import 'package:bottom_nav_fab_example/profile_page.dart';

// Placeholder Pages for demonstration if you don't create separate files
class HomePage extends StatelessWidget {
  const HomePage({super.key});
  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text(
        'Home Page Content',
        style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.blueAccent),
      ),
    );
  }
}

class SearchPage extends StatelessWidget {
  const SearchPage({super.key});
  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text(
        'Search Page Content',
        style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.green),
      ),
    );
  }
}

class ProfilePage extends StatelessWidget {
  const ProfilePage({super.key});
  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text(
        'Profile Page Content',
        style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.redAccent),
      ),
    );
  }
}


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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Bottom Nav with FAB',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _selectedIndex = 0;

  static const List<Widget> _widgetOptions = <Widget>[
    HomePage(),
    SearchPage(),
    ProfilePage(),
  ];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Bottom Nav with FAB'),
      ),
      body: Center(
        child: _widgetOptions.elementAt(_selectedIndex),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('FAB pressed!')),
          );
        },
        shape: const CircleBorder(),
        backgroundColor: Colors.amber, // Example color
        child: const Icon(Icons.add, color: Colors.white),
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
      bottomNavigationBar: BottomAppBar(
        shape: const CircularNotchedRectangle(),
        notchMargin: 8.0, // Increased margin for better visual separation
        color: Theme.of(context).primaryColor, // Use theme's primary color
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            IconButton(
              icon: Icon(Icons.home, color: _selectedIndex == 0 ? Colors.white : Colors.blue.shade100),
              onPressed: () => _onItemTapped(0),
              tooltip: 'Home',
            ),
            IconButton(
              icon: Icon(Icons.search, color: _selectedIndex == 1 ? Colors.white : Colors.blue.shade100),
              onPressed: () => _onItemTapped(1),
              tooltip: 'Search',
            ),
            // The FAB itself is between the navigation items, so we need a spacer
            const SizedBox(width: 48.0), // Adjust based on FAB size
            IconButton(
              icon: Icon(Icons.person, color: _selectedIndex == 2 ? Colors.white : Colors.blue.shade100),
              onPressed: () => _onItemTapped(2),
              tooltip: 'Profile',
            ),
            // If you had a fourth item, you'd place it here:
            // IconButton(
            //   icon: Icon(Icons.settings, color: _selectedIndex == 3 ? Colors.white : Colors.blue.shade100),
            //   onPressed: () => _onItemTapped(3),
            //   tooltip: 'Settings',
            // ),
          ],
        ),
      ),
    );
  }
}

Conclusion

By following these steps, you can successfully create a Bottom Navigation Bar with a centrally docked Floating Action Button and a circular notch in Flutter. This design pattern is highly effective for applications that have a primary action and multiple top-level navigation destinations, providing a clean, functional, and aesthetically pleasing user interface. Experiment with different colors, margins, and item arrangements to best suit your application's design language.

Related Articles

Aug 02, 2026

Building a Flutter Notification Center with Grouping & Actions

Learn to create a sophisticated in-app notification center in Flutter, featuring grouping, swipe-to-dismiss, custom icons, and actionable buttons for enhanced u

Aug 01, 2026

Flutter Layout Tips: Using FractionallySizedBox, AspectRatio, ConstrainedBox for Adaptive UI

Master Flutter's FractionallySizedBox, AspectRatio, and ConstrainedBox to build truly adaptive UIs. Learn how to control widget sizing, maintain proportions, an

Aug 01, 2026

Flutter Product Quick View: Animation, Related Items, Badges

Learn to create a dynamic product quick view widget in Flutter, incorporating smooth animations, relevant related items, and eye-catching promo badges for an en