Skip to content

Tabs & Drawer

In React you build tab bars and navigation drawers with third-party libraries or custom components — there is no built-in tab navigation primitive. Flutter ships first-class widgets for every common navigation chrome pattern: BottomNavigationBar (or NavigationBar in Material 3) for bottom tabs, TabBar + TabBarView for top tabs, and Drawer for a side navigation menu. All three work with Scaffold, which reserves the correct layout space automatically.

React
// React — bottom tabs (custom / library)
import { useState } from 'react';
const tabs = ['Home', 'Search', 'Profile'];
function App() {
const [activeTab, setActiveTab] = useState(0);
return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div style={{ flex: 1 }}>
{activeTab === 0 && <HomeScreen />}
{activeTab === 1 && <SearchScreen />}
{activeTab === 2 && <ProfileScreen />}
</div>
<nav>
{tabs.map((tab, i) => (
<button key={tab} onClick={() => setActiveTab(i)}>
{tab}
</button>
))}
</nav>
</div>
);
}
Flutter
// Flutter — BottomNavigationBar
class App extends StatefulWidget {
const App({super.key});
@override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
int _currentIndex = 0;
final _screens = const [
HomeScreen(),
SearchScreen(),
ProfileScreen(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _screens[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
);
}
}
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: MainShell());
  }
}

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

  @override
  State<MainShell> createState() => _MainShellState();
}

class _MainShellState extends State<MainShell> {
  int _index = 0;

  static const _screens = [
    _PlaceholderScreen(label: 'Home',    icon: Icons.home,   color: Colors.blue),
    _PlaceholderScreen(label: 'Search',  icon: Icons.search, color: Colors.green),
    _PlaceholderScreen(label: 'Profile', icon: Icons.person, color: Colors.purple),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_screens[_index].label),
      ),
      drawer: Drawer(
        child: ListView(
          children: const [
            DrawerHeader(
              decoration: BoxDecoration(color: Colors.blueGrey),
              child: Text('Menu', style: TextStyle(color: Colors.white, fontSize: 24)),
            ),
            ListTile(leading: Icon(Icons.settings), title: Text('Settings')),
            ListTile(leading: Icon(Icons.help),     title: Text('Help')),
          ],
        ),
      ),
      body: IndexedStack(index: _index, children: _screens),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _index,
        onTap: (i) => setState(() => _index = i),
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home),   label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
        ],
      ),
    );
  }
}

class _PlaceholderScreen extends StatelessWidget {
  final String label;
  final IconData icon;
  final Color color;

  const _PlaceholderScreen({
    required this.label,
    required this.icon,
    required this.color,
  });

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(icon, size: 64, color: color),
          const SizedBox(height: 12),
          Text(label, style: TextStyle(fontSize: 24, color: color,
              fontWeight: FontWeight.bold)),
        ],
      ),
    );
  }
}
Where do you pass a BottomNavigationBar widget in Flutter?
What does IndexedStack do differently from switching body: _screens[index]?
How does Flutter show the hamburger menu icon for a Drawer automatically?
What is the Material 3 replacement for BottomNavigationBar?