Flutter Tabs

Create tabbed interfaces for organized content

📑 What are Tabs?

Tabs organize content into separate views where only one view is visible at a time. Users can switch between tabs by tapping on them. Tabs are perfect for categorizing related content like chat conversations, news categories, or product listings in a clean, organized manner.


// Basic tab structure
TabBar(tabs: [Tab(text: 'Tab 1'), Tab(text: 'Tab 2')])
TabBarView(children: [Widget1(), Widget2()])
                                    

Key Tab Concepts

🎛️

TabController

Manages tab state and switching

TabController(
  length: 3,
  vsync: this
)
📊

TabBar

Displays the tab headers

TabBar(
  tabs: [Tab(...), Tab(...)]
)
📄

TabBarView

Shows tab content

TabBarView(
  children: [Widget1(), Widget2()]
)
🏷️

Tab

Individual tab item

Tab(
  icon: Icon(Icons.home),
  text: 'Home'
)

🔹 Basic Tabs

Create a simple tabbed interface with TabBar and TabBarView:

import 'package:flutter/material.dart';

class TabsDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3, // Number of tabs
      child: Scaffold(
        appBar: AppBar(
          title: Text('Tabs Demo'),
          bottom: TabBar(
            tabs: [
              Tab(icon: Icon(Icons.home), text: 'Home'),
              Tab(icon: Icon(Icons.star), text: 'Favorites'),
              Tab(icon: Icon(Icons.person), text: 'Profile'),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            Center(child: Text('Home Tab', style: TextStyle(fontSize: 30))),
            Center(child: Text('Favorites Tab', style: TextStyle(fontSize: 30))),
            Center(child: Text('Profile Tab', style: TextStyle(fontSize: 30))),
          ],
        ),
      ),
    );
  }
}

Result:

✅ Three tabs appear below the AppBar

✅ Tapping a tab switches the content

✅ Swipe gesture also switches tabs

🔹 Tabs with Custom Controller

Use TabController for more control over tab behavior:

class TabsWithController extends StatefulWidget {
  @override
  _TabsWithControllerState createState() => _TabsWithControllerState();
}

class _TabsWithControllerState extends State
    with SingleTickerProviderStateMixin {
  late TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
    
    // Listen to tab changes
    _tabController.addListener(() {
      if (!_tabController.indexIsChanging) {
        print('Current tab: ${_tabController.index}');
      }
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Custom Tab Controller'),
        bottom: TabBar(
          controller: _tabController,
          tabs: [
            Tab(text: 'Chats'),
            Tab(text: 'Status'),
            Tab(text: 'Calls'),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          Center(child: Text('Chats Content')),
          Center(child: Text('Status Content')),
          Center(child: Text('Calls Content')),
        ],
      ),
    );
  }
}

TabController Benefits:

  • Programmatically switch tabs: _tabController.animateTo(1)
  • Listen to tab changes
  • Get current tab index: _tabController.index
  • More control over animations

🔹 Styled Tabs

Customize tab appearance with colors and indicators:

AppBar(
  title: Text('Styled Tabs'),
  backgroundColor: Colors.purple,
  bottom: TabBar(
    tabs: [
      Tab(icon: Icon(Icons.directions_car), text: 'Car'),
      Tab(icon: Icon(Icons.directions_bike), text: 'Bike'),
      Tab(icon: Icon(Icons.directions_bus), text: 'Bus'),
    ],
    indicatorColor: Colors.white,
    indicatorWeight: 4.0,
    labelColor: Colors.white,
    unselectedLabelColor: Colors.white70,
    labelStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
    unselectedLabelStyle: TextStyle(fontSize: 14),
  ),
)

Styling Properties:

  • indicatorColor: Color of the selection indicator
  • indicatorWeight: Thickness of the indicator
  • labelColor: Color of selected tab text
  • unselectedLabelColor: Color of unselected tabs

🔹 Scrollable Tabs

Create scrollable tabs when you have many items:

DefaultTabController(
  length: 8,
  child: Scaffold(
    appBar: AppBar(
      title: Text('Scrollable Tabs'),
      bottom: TabBar(
        isScrollable: true, // Enable scrolling
        tabs: [
          Tab(text: 'Technology'),
          Tab(text: 'Sports'),
          Tab(text: 'Entertainment'),
          Tab(text: 'Business'),
          Tab(text: 'Health'),
          Tab(text: 'Science'),
          Tab(text: 'Politics'),
          Tab(text: 'Travel'),
        ],
      ),
    ),
    body: TabBarView(
      children: [
        Center(child: Text('Technology News')),
        Center(child: Text('Sports News')),
        Center(child: Text('Entertainment News')),
        Center(child: Text('Business News')),
        Center(child: Text('Health News')),
        Center(child: Text('Science News')),
        Center(child: Text('Politics News')),
        Center(child: Text('Travel News')),
      ],
    ),
  ),
)

Result:

✅ Tabs can be scrolled horizontally

✅ Perfect for many categories

🔹 Tabs with Icons Only

Create icon-only tabs for a cleaner look:

TabBar(
  tabs: [
    Tab(icon: Icon(Icons.camera_alt)),
    Tab(icon: Icon(Icons.chat)),
    Tab(icon: Icon(Icons.settings)),
  ],
)

🔹 Tabs in Body (Not AppBar)

Place tabs anywhere in your layout:

class TabsInBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 2,
      child: Scaffold(
        appBar: AppBar(title: Text('Tabs in Body')),
        body: Column(
          children: [
            Container(
              color: Colors.blue,
              child: TabBar(
                tabs: [
                  Tab(text: 'Overview'),
                  Tab(text: 'Details'),
                ],
              ),
            ),
            Expanded(
              child: TabBarView(
                children: [
                  Center(child: Text('Overview Content')),
                  Center(child: Text('Details Content')),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

🔹 Complete Example

A full working tabs implementation with real content:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

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

class TabsExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 4,
      child: Scaffold(
        appBar: AppBar(
          title: Text('My Social App'),
          bottom: TabBar(
            tabs: [
              Tab(icon: Icon(Icons.camera_alt), text: 'Camera'),
              Tab(icon: Icon(Icons.chat), text: 'Chats'),
              Tab(icon: Icon(Icons.circle), text: 'Status'),
              Tab(icon: Icon(Icons.call), text: 'Calls'),
            ],
          ),
        ),
        body: TabBarView(
          children: [
            CameraTab(),
            ChatsTab(),
            StatusTab(),
            CallsTab(),
          ],
        ),
      ),
    );
  }
}

class CameraTab extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(Icons.camera_alt, size: 100, color: Colors.blue),
          SizedBox(height: 20),
          Text('Camera', style: TextStyle(fontSize: 24)),
        ],
      ),
    );
  }
}

class ChatsTab extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 10,
      itemBuilder: (context, index) {
        return ListTile(
          leading: CircleAvatar(child: Text('${index + 1}')),
          title: Text('Contact ${index + 1}'),
          subtitle: Text('Last message...'),
          trailing: Text('12:30 PM'),
        );
      },
    );
  }
}

class StatusTab extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Status Updates', style: TextStyle(fontSize: 24)),
    );
  }
}

class CallsTab extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 5,
      itemBuilder: (context, index) {
        return ListTile(
          leading: CircleAvatar(
            child: Icon(Icons.person),
          ),
          title: Text('Contact ${index + 1}'),
          subtitle: Text('Yesterday, 3:45 PM'),
          trailing: Icon(Icons.call, color: Colors.green),
        );
      },
    );
  }
}

🧠 Test Your Knowledge

Which widget displays the content for each tab?