⚡ TL;DR
Implement TabBar and TabBarView in Flutter step by step — customize tab indicators, enable scrollable tabs, and change tabs programmatically.
Flutter, with its powerful set of widgets, offers an excellent framework for building visually appealing and efficient user interfaces. One standout feature that contributes to seamless app navigation and organization is the combination of TabBar and TabBarView. In this comprehensive guide, we will walk you through the step-by-step process of implementing and mastering TabBar and TabBarView in your Flutter app. We will also explore various customization options and best practices to keep your app up-to-date.
Setting up TabBar in Flutter
To get started, let’s lay the foundation for integrating TabBar into your Flutter app. We’ll create a basic example with three tabs:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text('Flutter TabBar Example'),
bottom: TabBar(
tabs: [
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
],
),
),
body: TabBarView(
children: [
Center(child: Text('Content for Tab 1')),
Center(child: Text('Content for Tab 2')),
Center(child: Text('Content for Tab 3')),
],
),
),
),
);
}
}How to Customize the Tab Indicator in TabBar
Tab Color
You can set the color of the selected and unselected tabs using the 'indicatorColor' and 'unselectedLabelColor' properties of the 'TabBar' widget. For instance:
TabBar(
indicatorColor: Colors.blue,
unselectedLabelColor: Colors.grey,
tabs: [...],
),Tab Size
You can adjust the size of the tabs using the 'labelPadding' property. For example:
TabBar(
labelPadding: EdgeInsets.symmetric(
horizontal: 16.0,
),
tabs: [...],
),Tab Height
Modify the height of the tabs using the 'indicatorWeight' property:
TabBar(
indicatorWeight: 3.0,
tabs: [...],
),Change the Indicator
Customize the appearance of the tab indicator using the 'indicator' property. You can create your own 'BoxDecoration' for a unique look:
TabBar(
indicator: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(8.0),
),
tabs: [...],
),TabBar Background Color
TabBar has no backgroundColor property of its own. To alter the background color of the 'TabBar', either set the background color on the enclosing AppBar (the tab bar sits in the AppBar’s bottom, so it inherits that color), or wrap the TabBar in a Container when it stands alone:
// Option 1: color the AppBar that hosts the TabBar
AppBar(
backgroundColor: Colors.blue,
title: Text('Flutter TabBar Example'),
bottom: TabBar(
tabs: [...],
),
),// Option 2: wrap a standalone TabBar in a Container
Container(
color: Colors.grey[200],
child: TabBar(
tabs: [...],
),
),Background Image
For a visually appealing touch, you can set a background image for the 'TabBar' using a 'Container' with a 'decoration':
AppBar(
title: Text('Flutter TabBar Example'),
bottom: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/tab_bar_background.jpg'),
fit: BoxFit.cover,
),
),
child: TabBar(
tabs: [...],
),
),
),Custom Indicator
Create a custom tab indicator using the 'indicator' property of 'TabBar'. This allows you to design unique indicators that align with your app’s aesthetics:
TabBar(
indicator: MyCustomIndicator(),
tabs: [...],
),Making Scrollable Tabs with TabBar
Horizontally Scrollable Tabs
When you have numerous tabs, enable horizontal scrolling with the TabBar’s own 'isScrollable' property (avoid wrapping the TabBar in a 'SingleChildScrollView' — that desynchronizes the indicator from the selected tab):
TabBar(
isScrollable: true,
tabs: [...],
),Vertically Scrollable Tabs
In scenarios where vertical space is limited, you can apply vertical scrolling to your 'TabBar' using a 'ListView' or 'CustomScrollView':
ListView(
scrollDirection: Axis.vertical,
children: [
TabBar(
tabs: [...],
),
// Other content
],
),Changing Tabs Programmatically
You can programmatically change the selected tab using the 'DefaultTabController' and its 'animateTo' method. This is particularly useful when you want to navigate to a specific tab based on user interactions:
// DefaultTabController.of(context) returns a nullable TabController,
// so guard the call before animating.
final TabController? controller = DefaultTabController.of(context);
controller?.animateTo(1); // Navigates to Tab 2Listening for Tab Change Event
To listen for tab change events, create your own 'TabController' with a TickerProvider and attach a listener with 'addListener'. Note that context is not available in initState, so you can’t fetch a DefaultTabController there — creating the controller yourself avoids that problem entirely:
class MyTabsPage extends StatefulWidget {
const MyTabsPage({super.key});
State<MyTabsPage> createState() => _MyTabsPageState();
}
class _MyTabsPageState extends State<MyTabsPage>
with SingleTickerProviderStateMixin {
late final TabController _tabController;
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
_tabController.addListener(_onTabChanged);
}
void _onTabChanged() {
// indexIsChanging is true while an animation is in flight;
// react only when the tap has settled.
if (_tabController.indexIsChanging) return;
// Do something when tab changes
}
void dispose() {
_tabController.removeListener(_onTabChanged);
_tabController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter TabBar Example'),
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
],
),
),
body: TabBarView(
controller: _tabController,
children: const [
Center(child: Text('Content for Tab 1')),
Center(child: Text('Content for Tab 2')),
Center(child: Text('Content for Tab 3')),
],
),
);
}
}TabBar.secondary: Top vs Bottom Tabs
Since Flutter 3.x, TabBar has a secondary property that changes its appearance to suit placement at the bottom of the screen (as Material 3 navigation), where the indicator sits at the top edge of the tab bar instead of the bottom:
Scaffold(
appBar: AppBar(
title: const Text('Bottom Tabs'),
),
bottomNavigationBar: Material(
color: Theme.of(context).colorScheme.surface,
child: TabBar.secondary(
controller: _tabController,
tabs: const [
Tab(icon: Icon(Icons.home), text: 'Home'),
Tab(icon: Icon(Icons.search), text: 'Search'),
Tab(icon: Icon(Icons.settings), text: 'Settings'),
],
),
),
body: TabBarView(
// Same controller drives both the tab bar and the pages
controller: _tabController,
children: const [
Center(child: Text('Home')),
Center(child: Text('Search')),
Center(child: Text('Settings')),
],
),
),Use a primary TabBar in an AppBar.bottom and TabBar.secondary for bottom navigation bars.
How to Implement TabBar Without AppBar
Should you require a standalone TabBar outside of an AppBar, you can place it within a 'Container' or 'Card' to create a distinctive visual separation:
Container(
color: Colors.grey[200],
child: TabBar(
tabs: [...],
),
),Preserving the State of Tabs
To maintain the state of tabs when switching between them, you can employ the 'AutomaticKeepAliveClientMixin' along with the 'wantKeepAlive' method. This ensures that the state of each tab is retained even when navigating back and forth:
class Tab1Content extends StatefulWidget {
_Tab1ContentState createState() => _Tab1ContentState();
}
class _Tab1ContentState extends State<Tab1Content>
with AutomaticKeepAliveClientMixin<Tab1Content> {
bool get wantKeepAlive => true;
Widget build(BuildContext context) {
super.build(context); // Make sure to call super.build
return Center(child: Text('Content for Tab 1'));
}
}Conclusion
In this guide, we’ve covered the step-by-step process of implementing and customizing TabBar and TabBarView in your Flutter app. From setting up the basic structure to handling customization, scrollable tabs, and maintaining tab state, you’re now equipped with the knowledge to create seamless and interactive tab-based navigation in your Flutter applications. As you continue your journey in Flutter development, remember that mastering TabBar and TabBarView will significantly enhance the user experience and contribute to the overall success of your apps.
Related Posts
- A Comprehensive Guide to Flutter Buttons: Choosing the Right One for Your App
- How to Change minSdkVersion in Flutter: 2 Simple Approaches
- All about SOLID Principles in Flutter: Examples and Tips
- Enhancing File Manipulation in Flutter: Best Practices and Examples
References
- TabBar class — Flutter API docs
- TabBarView class — Flutter API docs
- DefaultTabController class — Flutter API docs
- TabController class — Flutter API docs
- AutomaticKeepAliveClientMixin — Flutter API docs
FAQ
How do I create tabs in Flutter?
Wrap your screen in a DefaultTabController with a length matching the tab count, place a TabBar in the AppBar’s bottom property with one Tab widget per tab, and supply a TabBarView as the body with one content widget per tab. DefaultTabController manages the controller for you, keeping the tab bar and the page content synchronized automatically.
How do I make scrollable tabs in Flutter?
Set isScrollable to true on the TabBar itself. This lets tabs size to their content and scroll horizontally instead of compressing to fit the screen width. Avoid wrapping the TabBar in a SingleChildScrollView for this purpose — it desynchronizes the indicator from the selected tab and breaks tap-target behavior.
How do I change tabs programmatically in Flutter?
Retrieve the TabController and call animateTo with the target index. With a DefaultTabController, use DefaultTabController.of(context) — which returns a nullable controller, so guard the call — then invoke controller?.animateTo(1) to jump to the second tab. If you created your own TabController in initState, simply call animateTo on it directly.
How do I preserve tab state when switching tabs in Flutter?
Add the AutomaticKeepAliveClientMixin to each tab’s state class and return true from the wantKeepAlive getter, calling super.build inside the build method. Without this, TabBarView disposes off-screen tab content and rebuilds it on return, losing scroll position and form state. This mixin keeps each tab’s state alive while the user navigates between tabs.

Written by Mihir Pipermitwala
Senior Full Stack Consultant & Mobile Engineering Specialist building user-focused applications in healthcare with Flutter, Dart and React Native. I write about what I actually build and test — every tutorial here comes from real project work.
More about Mihir →