Flutter TabBar and TabBarView: Swipeable Tabs Step by Step

Coding Liquids blog cover featuring Sagnik Bhattacharya for the Flutter TabBar and TabBarView tutorial, with a tab bar in the AppBar bottom slot and swipeable views synced by a TabController.
Coding Liquids blog cover featuring Sagnik Bhattacharya for the Flutter TabBar and TabBarView tutorial.

Tabs across the top — swipe or tap between them — are how apps split a screen into a few related views: a profile's Posts, Media, and Likes, or a store's Overview and Reviews. Flutter gives you TabBar for the row of tabs and TabBarView for the swipeable pages, kept in sync by a TabController. This guide covers the quick DefaultTabController approach, when you need a manual controller, scrollable tabs, and the length-mismatch error everyone hits once, with paste-ready code.

The Complete Flutter Guide course thumbnail

The Complete Flutter Guide: Build Android, iOS and Web apps

Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.

Enrol now

Every snippet below is paste-ready against current stable Flutter. Tabs sit in the AppBar's bottom slot and complement, rather than replace, the bottom navigation bar — top tabs split one section, the bottom bar splits the app.

Follow me on Instagram@sagnikteaches

We'll build tabs with DefaultTabController, switch to a manual TabController when we need to listen, make the tabs scrollable, and fix the length-mismatch assertion.

Connect on LinkedInSagnik Bhattacharya

If you'd rather watch a tabbed profile screen built and styled, the channel covers these layouts inside real apps.

Subscribe on YouTube@codingliquids

The easy way: DefaultTabController

For self-contained tabs, DefaultTabController creates and shares the controller for you. Set its length, drop a TabBar in the AppBar's bottom slot and a TabBarView in the body, and tapping and swiping stay in sync automatically.

DefaultTabController(
  length: 3,
  child: Scaffold(
    appBar: AppBar(
      title: const Text('Profile'),
      bottom: const TabBar(
        tabs: [
          Tab(text: 'Posts'),
          Tab(text: 'Media'),
          Tab(text: 'Likes'),
        ],
      ),
    ),
    body: const TabBarView(
      children: [PostsTab(), MediaTab(), LikesTab()],
    ),
  ),
)

That's the whole pattern. The TabBar and TabBarView find the shared controller through the build context, so a tap scrolls the views and a swipe highlights the tab — no manual synchronisation.

When you need a manual TabController

Reach for a manual TabController when you must react to tab changes — load data when a tab opens, or drive the selection from a button. Create it in a StatefulWidget with SingleTickerProviderStateMixin, and dispose it.

class _ProfileState extends State<Profile>
    with SingleTickerProviderStateMixin {
  late final TabController _controller =
      TabController(length: 3, vsync: this);

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        bottom: TabBar(controller: _controller, tabs: const [/* ... */]),
      ),
      body: TabBarView(controller: _controller, children: const [/* ... */]),
    );
  }
}

Pass the same _controller to both the bar and the views so they stay in lockstep, and call _controller.animateTo(index) to switch tabs from code. Disposing the controller in dispose is what prevents a memory leak.

The Complete Flutter Guide course thumbnail

Build polished, tabbed UIs

The Complete Flutter Guide covers tabs, navigation, and state from first principles to shipped apps.

Enrol now

Scrollable tabs

By default the tabs spread to fill the width, which crowds them once you have many. Set isScrollable: true and each tab sizes to its label while the row scrolls horizontally — ideal for category bars.

TabBar(
  isScrollable: true,
  tabs: categories.map((c) => Tab(text: c)).toList(),
)

This is the right mode for a news app's sections or a shop's many departments. For three or four short labels, leave it off so the tabs fill the bar evenly.

Styling the tabs

TabBar exposes indicatorColor, indicatorWeight, and labelColor / unselectedLabelColor, plus a custom indicator for pill or rounded styles. On Material 3 the defaults already follow your theme, so you often need only a colour tweak rather than a full custom indicator.

Common mistakes

  • Length not matching the tabs and views. The controller length, the Tab count, and the TabBarView children must all be equal.
  • Forgetting to dispose a manual TabController. Always dispose it to avoid a leak.
  • Using two controllers. The bar and the views must share one controller, or they desync.
  • Crowding many tabs without isScrollable. Turn it on for six or more tabs.
  • Confusing tabs with bottom navigation. Tabs split a section; the bottom bar splits the app.

Frequently asked questions

How do I create tabs in Flutter?

Wrap the screen in a DefaultTabController, put a TabBar in the AppBar's bottom slot and a matching TabBarView in the body; they sync automatically.

What is the difference between DefaultTabController and TabController in Flutter?

DefaultTabController provides a controller for you; a manual TabController is for when you must listen to or drive the selection from code.

How do I make Flutter tabs scrollable?

Set isScrollable: true on the TabBar so each tab sizes to its label and the row scrolls horizontally — best for many tabs.

Why do my Flutter tabs throw a length mismatch error?

The controller's length must equal both the Tab count and the TabBarView children count. Keep all three identical.

Further reads

Keep going with the tutorials that pair with this guide:

Sources: Flutter documentation — TabBar, TabBarView, TabController, DefaultTabController, and the "Work with tabs" cookbook recipe (docs.flutter.dev). Verified against current stable Flutter.