Scaffold, AppBar & Body
Understanding the Scaffold Widget
The Scaffold widget is one of the most essential widgets in Flutter. It provides the basic visual structure for a Material Design app, including slots for the app bar, body, floating action button, drawer, bottom navigation bar, and bottom sheet. Almost every Flutter screen you build will use a Scaffold as its root layout widget.
Think of Scaffold as the skeleton of your app screen. It handles positioning of common UI elements so you can focus on the actual content.
Basic Scaffold Structure
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('My First Scaffold'),
),
body: const Center(
child: Text('Hello, Scaffold!'),
),
),
);
}
}
The Scaffold widget accepts many named parameters. Here are the most commonly used ones:
appBar-- A widget displayed at the top of the screen, usually anAppBar.body-- The primary content of the screen, displayed below the app bar.floatingActionButton-- A button that floats above the body, typically for the primary action.drawer-- A side panel that slides in from the left (or right in RTL).bottomNavigationBar-- A navigation bar displayed at the bottom of the screen.bottomSheet-- A persistent sheet displayed at the bottom of the screen.backgroundColor-- The background color of the entire scaffold.
Scaffold with All Major Properties
Let us build a more complete example that demonstrates the key Scaffold properties together:
Complete Scaffold Example
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
backgroundColor: Colors.indigo,
foregroundColor: Colors.white,
),
body: const Center(
child: Text(
'Welcome to the app!',
style: TextStyle(fontSize: 24),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
debugPrint('FAB pressed!');
},
child: const Icon(Icons.add),
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
backgroundColor: Colors.grey[100],
);
}
}
floatingActionButtonLocation property lets you control where the FAB is positioned. Options include endFloat (default, bottom-right), centerFloat (bottom-center), endDocked, centerDocked, and more.Deep Dive into AppBar
The AppBar widget is the toolbar displayed at the top of the screen. It is the most common widget used in the appBar slot of a Scaffold. AppBar provides areas for a title, navigation icons, and action buttons.
AppBar Properties Overview
AppBar(
// The main title widget
title: const Text('App Title'),
// Widget on the left (back button auto-added with Navigator)
leading: IconButton(
icon: const Icon(Icons.menu),
onPressed: () {},
),
// Widgets on the right side
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () {},
),
IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () {},
),
],
// Elevation (shadow depth)
elevation: 4.0,
// Colors
backgroundColor: Colors.deepPurple,
foregroundColor: Colors.white,
// Center the title (default varies by platform)
centerTitle: true,
)
Key AppBar properties explained:
title-- The primary widget displayed in the app bar. Usually aTextwidget, but can be any widget (a search field, logo, etc.).leading-- A widget displayed before the title. If null and there is a Drawer, a menu icon is auto-added. If the current route can be popped, a back arrow is auto-added.actions-- A list of widgets displayed after the title. TypicallyIconButtonwidgets for toolbar actions.elevation-- The z-coordinate of the app bar, controlling the shadow. Set to0for a flat look.centerTitle-- Whether to center the title. Defaults totrueon iOS andfalseon Android.flexibleSpace-- A widget stacked behind the toolbar and tab bar, useful for background effects.toolbarHeight-- The height of the toolbar area (default iskToolbarHeightwhich is 56.0).
Custom AppBar with FlexibleSpace
The flexibleSpace property allows you to place a widget behind the AppBar content. This is often used for gradient backgrounds or images.
AppBar with Gradient Background
AppBar(
title: const Text('Gradient AppBar'),
centerTitle: true,
foregroundColor: Colors.white,
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple, Colors.deepPurple],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
),
elevation: 0,
)
PreferredSize Widget
Sometimes you need a custom app bar that does not use the standard AppBar widget. The PreferredSize widget tells the Scaffold how tall your custom app bar should be.
Custom AppBar with PreferredSize
Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(80),
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.lightBlueAccent],
),
),
child: const SafeArea(
child: Center(
child: Text(
'Custom App Bar',
style: TextStyle(
color: Colors.white,
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
body: const Center(
child: Text('Body Content'),
),
)
PreferredSize when you need full control over the app bar layout. Wrap your custom widget with SafeArea to avoid overlapping with the device status bar.SliverAppBar Basics
The SliverAppBar is an advanced app bar that integrates with CustomScrollView to create scroll-based effects like collapsing, floating, and pinning. It is used inside a CustomScrollView instead of the Scaffold’s appBar property.
Basic SliverAppBar
class SliverExample extends StatelessWidget {
const SliverExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200,
floating: false,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: const Text('SliverAppBar Demo'),
background: Image.network(
'https://picsum.photos/800/400',
fit: BoxFit.cover,
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(
title: Text('Item \${index + 1}'),
),
childCount: 30,
),
),
],
),
);
}
}
Key SliverAppBar properties:
expandedHeight-- The height of the app bar when fully expanded.floating-- If true, the app bar becomes visible as soon as the user scrolls up.pinned-- If true, the app bar remains visible (collapsed) at the top when scrolling down.snap-- If true (requiresfloating: true), the app bar snaps fully open or closed.flexibleSpace-- Usually aFlexibleSpaceBarwith a title and background image.
SliverAppBar must be used inside a CustomScrollView, not as the appBar property of Scaffold. If you place it in the Scaffold’s appBar slot, you will get a type error because SliverAppBar is not a PreferredSizeWidget.Scaffold Drawer
The drawer property adds a navigation drawer that slides in from the left edge. When a drawer is provided, the AppBar automatically adds a hamburger menu icon as the leading widget.
Scaffold with Drawer
Scaffold(
appBar: AppBar(
title: const Text('Drawer Example'),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text(
'App Menu',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
ListTile(
leading: const Icon(Icons.home),
title: const Text('Home'),
onTap: () {
Navigator.pop(context);
},
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Settings'),
onTap: () {
Navigator.pop(context);
},
),
],
),
),
body: const Center(
child: Text('Swipe from left or tap the menu icon'),
),
)
Scaffold Bottom Navigation Bar
The bottomNavigationBar property adds a navigation bar at the bottom of the screen, commonly used for switching between main sections of an app.
Bottom Navigation Bar Example
class MainScreen extends StatefulWidget {
const MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int _selectedIndex = 0;
final List<Widget> _pages = const [
Center(child: Text('Home Page')),
Center(child: Text('Search Page')),
Center(child: Text('Profile Page')),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Bottom Nav Example'),
),
body: _pages[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) {
setState(() {
_selectedIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.search),
label: 'Search',
),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: 'Profile',
),
],
),
);
}
}
Scaffold Bottom Sheet
A bottomSheet is a persistent panel that stays visible at the bottom of the screen. For modal bottom sheets that slide up temporarily, use showModalBottomSheet() instead.
Persistent vs Modal Bottom Sheet
// Persistent bottom sheet (always visible)
Scaffold(
appBar: AppBar(title: const Text('Bottom Sheet')),
body: const Center(child: Text('Main Content')),
bottomSheet: Container(
height: 60,
color: Colors.grey[200],
child: const Center(
child: Text('This is a persistent bottom sheet'),
),
),
)
// Modal bottom sheet (triggered by user action)
ElevatedButton(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return Container(
height: 200,
padding: const EdgeInsets.all(16),
child: const Column(
children: [
Text(
'Modal Bottom Sheet',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16),
Text('This slides up and can be dismissed.'),
],
),
);
},
);
},
child: const Text('Show Modal Sheet'),
)
Practical Example: Complete App Shell
Let us combine everything into a realistic app shell that uses Scaffold with AppBar, Drawer, FAB, and BottomNavigationBar:
Complete App Shell
class AppShell extends StatefulWidget {
const AppShell({super.key});
@override
State<AppShell> createState() => _AppShellState();
}
class _AppShellState extends State<AppShell> {
int _currentIndex = 0;
final List<String> _titles = ['Dashboard', 'Projects', 'Messages'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_titles[_currentIndex]),
actions: [
IconButton(
icon: const Icon(Icons.notifications_outlined),
onPressed: () {},
),
],
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const UserAccountsDrawerHeader(
accountName: Text('John Doe'),
accountEmail: Text('john@example.com'),
currentAccountPicture: CircleAvatar(
child: Text('J'),
),
),
ListTile(
leading: const Icon(Icons.dashboard),
title: const Text('Dashboard'),
onTap: () => _selectPage(0),
),
ListTile(
leading: const Icon(Icons.folder),
title: const Text('Projects'),
onTap: () => _selectPage(1),
),
ListTile(
leading: const Icon(Icons.message),
title: const Text('Messages'),
onTap: () => _selectPage(2),
),
const Divider(),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Settings'),
onTap: () {
Navigator.pop(context);
},
),
],
),
),
body: Center(
child: Text(
'\${_titles[_currentIndex]} Content',
style: const TextStyle(fontSize: 24),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.dashboard),
label: 'Dashboard',
),
BottomNavigationBarItem(
icon: Icon(Icons.folder),
label: 'Projects',
),
BottomNavigationBarItem(
icon: Icon(Icons.message),
label: 'Messages',
),
],
),
);
}
void _selectPage(int index) {
setState(() {
_currentIndex = index;
});
Navigator.pop(context); // Close drawer
}
}
UserAccountsDrawerHeader instead of a plain DrawerHeader when you want to display user profile information with an avatar, name, and email. It provides a polished look with minimal effort.Practice Exercise
Build a Scaffold-based screen with the following features: (1) An AppBar with a gradient background using flexibleSpace, a centered title, and two action icons. (2) A Drawer with at least three navigation items. (3) A body that displays different content based on the selected drawer item. (4) A FloatingActionButton positioned at centerFloat. Challenge: Add a BottomNavigationBar with three tabs and sync it with the drawer navigation.