image

27 Dec 2025

9K

35K

Building a TabBar Widget with Badges in Flutter

Flutter's TabBar widget is a fundamental component for navigation within applications, allowing users to switch between different views easily. While the default Tab widget provides basic icon and text options, many modern applications require visual indicators like badges to highlight unread messages, pending notifications, or new content. This article will guide you through building a custom TabBar with badges, enhancing user experience and providing crucial visual cues.

Understanding the Basic TabBar

Before diving into badges, let's briefly review the standard TabBar implementation. It typically works in conjunction with a TabController and a TabBarView to synchronize the tab selection with the displayed content.

Here's a basic structure:


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DefaultTabController(
        length: 3, // Number of tabs
        child: Scaffold(
          appBar: AppBar(
            title: Text('Basic TabBar'),
            bottom: TabBar(
              tabs: [
                Tab(icon: Icon(Icons.home), text: 'Home'),
                Tab(icon: Icon(Icons.notifications), text: 'Notifications'),
                Tab(icon: Icon(Icons.settings), text: 'Settings'),
              ],
            ),
          ),
          body: TabBarView(
            children: [
              Center(child: Text('Home Content')),
              Center(child: Text('Notifications Content')),
              Center(child: Text('Settings Content')),
            ],
          ),
        ),
      ),
    );
  }
}

In this setup, each Tab widget receives an Icon and Text. To add a badge, we need to create a custom widget that combines these elements with a badge indicator.

Creating a Custom Tab Widget with Badge

The key to adding badges is to create a custom widget that will serve as the child of each Tab. This custom widget will use a Stack to overlay a small badge container on top of the tab's icon and text.

We'll define a CustomTabWithBadge widget. This widget takes an icon, text, and an optional badgeCount. If badgeCount is greater than 0, a red circular badge displaying the count will be shown.


import 'package:flutter/material.dart';

class CustomTabWithBadge extends StatelessWidget {
  final IconData icon;
  final String text;
  final int badgeCount;

  const CustomTabWithBadge({
    Key? key,
    required this.icon,
    required this.text,
    this.badgeCount = 0,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Tab(
      child: Stack(
        // Allow the badge to overflow the Stack's bounds, so it can sit at the corner of the tab.
        clipBehavior: Clip.none, 
        alignment: Alignment.center,
        children: [
          // The main content of the tab (icon and text)
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(icon),
              Text(text),
            ],
          ),
          // The badge widget, conditionally displayed
          if (badgeCount > 0)
            Positioned(
              right: -5, // Adjust positioning as needed to place it effectively
              top: -5,  // Adjust positioning as needed
              child: Container(
                padding: EdgeInsets.all(2.0),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(10.0),
                  color: Colors.red,
                ),
                constraints: BoxConstraints(
                  minWidth: 16,
                  minHeight: 16,
                ),
                child: Text(
                  badgeCount.toString(),
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 10,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

Key aspects of the CustomTabWithBadge widget:

  • It returns a Tab widget, making it compatible with TabBar.tabs.
  • Inside the Tab, a Stack is used to layer widgets.
  • clipBehavior: Clip.none is crucial. It allows the badge (which is a Positioned widget) to extend beyond the nominal bounds of the Stack, making it appear at the corner of the tab itself, rather than being confined within the icon/text column.
  • The Positioned widget's right and top properties are adjusted to place the badge precisely at the desired corner. These values might need fine-tuning based on your design.
  • The badge itself is a small Container with rounded corners, containing a Text widget for the count.

Integrating Badges into a Stateful TabBar

To make the badges dynamic (e.g., updating counts), we'll use a StatefulWidget and manage the badge counts within its state. We'll also use a TabController for more control over tab selection.


import 'package:flutter/material.dart';

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

// CustomTabWithBadge widget as defined above
class CustomTabWithBadge extends StatelessWidget {
  final IconData icon;
  final String text;
  final int badgeCount;

  const CustomTabWithBadge({
    Key? key,
    required this.icon,
    required this.text,
    this.badgeCount = 0,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Tab(
      child: Stack(
        clipBehavior: Clip.none,
        alignment: Alignment.center,
        children: [
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(icon),
              Text(text),
            ],
          ),
          if (badgeCount > 0)
            Positioned(
              right: -5,
              top: -5,
              child: Container(
                padding: EdgeInsets.all(2.0),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(10.0),
                  color: Colors.red,
                ),
                constraints: BoxConstraints(
                  minWidth: 16,
                  minHeight: 16,
                ),
                child: Text(
                  badgeCount.toString(),
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 10,
                    color: Colors.white,
                  ),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'TabBar with Badge Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TabBarBadgeScreen(),
    );
  }
}

class TabBarBadgeScreen extends StatefulWidget {
  @override
  _TabBarBadgeScreenState createState() => _TabBarBadgeScreenState();
}

class _TabBarBadgeScreenState extends State with SingleTickerProviderStateMixin {
  late TabController _tabController;
  int _notificationBadgeCount = 5; // Initial badge count for notifications
  int _messageBadgeCount = 2;       // Initial badge count for messages

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
  }

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

  void _incrementNotificationBadge() {
    setState(() {
      _notificationBadgeCount++;
    });
  }

  void _resetBadges() {
    setState(() {
      _notificationBadgeCount = 0;
      _messageBadgeCount = 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TabBar with Badges'),
        bottom: TabBar(
          controller: _tabController,
          tabs: [
            CustomTabWithBadge(icon: Icons.home, text: 'Home'),
            CustomTabWithBadge(icon: Icons.notifications, text: 'Notifications', badgeCount: _notificationBadgeCount),
            CustomTabWithBadge(icon: Icons.message, text: 'Messages', badgeCount: _messageBadgeCount),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          Center(child: Text('Home Content')),
          Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Notifications Content'),
                ElevatedButton(
                  onPressed: _incrementNotificationBadge,
                  child: Text('Add Notification'),
                ),
                ElevatedButton(
                  onPressed: _resetBadges,
                  child: Text('Reset All Badges'),
                ),
              ],
            ),
          ),
          Center(child: Text('Messages Content')),
        ],
      ),
      // Example of programmatic tab control
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // Programmatically switch to the notifications tab (index 1)
          _tabController.animateTo(1); 
        },
        child: Icon(Icons.send),
      ),
    );
  }
}

In this complete example:

  • The TabBarBadgeScreen is a StatefulWidget to hold the badge counts in its state.
  • SingleTickerProviderStateMixin is used to provide the vsync property required by TabController.
  • _notificationBadgeCount and _messageBadgeCount are state variables that control the badge visibility and number.
  • The TabBar's tabs list now uses our CustomTabWithBadge, passing the current badge counts.
  • Buttons are added to demonstrate how you can increment or reset badge counts, triggering a setState call to rebuild the UI and update the badges.
  • A FloatingActionButton demonstrates programmatically switching tabs using _tabController.animateTo().

Conclusion

By leveraging Flutter's powerful composition model, building custom widgets like a TabBar with badges is straightforward. This approach not only allows you to integrate dynamic visual indicators for notifications or unread counts but also opens the door to further customization of your tabs. This enhanced TabBar improves the clarity and interactivity of your application, providing a more intuitive and informative user experience.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is