Flutter: AppBar Widget and Customization
Last updated:
6 min read

AppBar is the top bar you pass to the appBar parameter of a Scaffold. It tells the user which screen they are on, carries the back or menu button, and collects screen-specific actions (search, share, delete) on the right. You start with a single title; as the screen grows, leading, actions and bottom come into play. This post walks through those slots, the color behavior that arrived with Material 3, and the mistakes that show up most often.
Live Demo
You can try this widget in the interactive example below:
💡 If the example above doesn't load, click DartPad to run it in a new tab.
Basic Usage
Scaffold(
appBar: AppBar(
title: const Text('Page Title'),
),
body: const Center(child: Text('Content')),
)When the AppBar lives in a Scaffold, the status bar inset, the height and the back button are handled for you. A page opened with Navigator gets a back arrow automatically, and a Scaffold with a drawer gets a menu icon.
Important Properties
| Property | Description |
|---|---|
title |
Title widget (usually a Text) |
leading |
Widget on the left (back button, menu icon) |
actions |
List of buttons on the right |
bottom |
Area attached below the bar (TabBar, progress bar) |
backgroundColor |
Background color |
foregroundColor |
Color of the title and icons |
elevation / shadowColor |
Shadow amount and shadow color |
scrolledUnderElevation |
Elevation applied when content scrolls under the bar |
centerTitle |
Centers the title |
automaticallyImplyLeading |
Turns the automatic back/menu button on or off |
toolbarHeight |
Height of the bar |
Using Actions
actions is a list of widgets. Keep it to two, at most three icons and move the rest into an overflow menu. PopupMenuButton is made for exactly that:
AppBar(
title: const Text('Title'),
actions: [
IconButton(
icon: const Icon(Icons.search),
tooltip: 'Search',
onPressed: () {},
),
PopupMenuButton<String>(
onSelected: (value) {},
itemBuilder: (context) => const [
PopupMenuItem(value: 'settings', child: Text('Settings')),
PopupMenuItem(value: 'help', child: Text('Help')),
],
),
],
)Don't skip tooltip: it shows a hint on long press, and screen readers announce the button with that text.
Customizing Leading
Once you fill leading yourself, the automatic back and menu button is switched off and the behavior is yours to write. Opening the menu needs Scaffold.of(context), and that call must use a context below the Scaffold, which is why a Builder is involved:
AppBar(
leading: Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
title: const Text('Title'),
)I cover that pitfall in detail in the Drawer post. If you put a text button such as "Cancel" there instead of an icon, the slot is too narrow; widen it with leadingWidth.
The bottom Slot
bottom expects a PreferredSizeWidget, a widget that announces its height up front. TabBar already implements it, so on tabbed screens it goes straight into bottom. Wrap your own widget in PreferredSize:
AppBar(
title: const Text('Orders'),
bottom: const PreferredSize(
preferredSize: Size.fromHeight(4),
child: LinearProgressIndicator(),
),
)Search Field and Gradient
Because title accepts any widget, a TextField works there. For a gradient background use flexibleSpace, which is painted behind the bar:
AppBar(
title: const Text('Gradient'),
foregroundColor: Colors.white,
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Colors.purple, Colors.blue],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
),
)Color and Shadow in Material 3
In Material 3 the AppBar defaults to colorScheme.surface with no shadow. When content scrolls under it, the bar enters the "scrolled under" state: scrolledUnderElevation kicks in and the surface shifts by one tone. Depending on the Flutter version this shows up as a surface tint overlay or as a different surface tone. If your design wants a bar that never changes color, pin it down in the theme:
final scheme = ColorScheme.fromSeed(seedColor: Colors.indigo);
ThemeData(
colorScheme: scheme,
appBarTheme: AppBarTheme(
backgroundColor: scheme.surface,
foregroundColor: scheme.onSurface,
scrolledUnderElevation: 0,
surfaceTintColor: Colors.transparent,
),
)The second benefit of AppBarTheme is that you stop repeating the same colors on every screen.
When to Use It and When Not To
AppBar is the right choice for any standard screen with a title and a few actions. Look elsewhere in these cases:
- If the header should shrink on scroll, carry a large image, or come back when the user scrolls up, you need
SliverAppBar. I covered that in SliverAppBar with a category header and search field. - Full-screen imagery, onboarding or a video player may need no bar at all; leave
appBarempty and protect the content withSafeArea. - If content should show through the bar, set
extendBodyBehindAppBar: trueon theScaffoldand usebackgroundColor: Colors.transparent. - If there is one primary action and it is the main job of the screen, make it a
FloatingActionButtoninstead of burying it inactions.
Common Mistakes
1. The bar changes color when the list scrolls. Symptom: the bar is white when the page opens and turns grayish or tinted after a small scroll. That is not a bug but the Material 3 behavior described above. If you don't want it, set backgroundColor explicitly and add scrolledUnderElevation: 0.
2. I set elevation but there is no shadow. The default shadow color in Material 3 is transparent. If you want a shadow, name its color too:
AppBar(
title: const Text('With shadow'),
elevation: 4,
shadowColor: Colors.black54,
)3. I changed the background and now the title and icons are unreadable. Setting a dark backgroundColor while the text stays in its default dark color is very common. Instead of styling each Text, use foregroundColor; it paints the title, the back arrow and the actions icons together:
AppBar(
title: const Text('Profile'),
backgroundColor: Colors.indigo,
foregroundColor: Colors.white,
)4. The back button vanished after I set a custom leading. Providing leading disables the automatic button completely. Either write the back behavior (Navigator.pop(context)) yourself, or pass leading only when you need it and leave it null otherwise. The scenario below does exactly that.
Mini Scenario: Multi-Select Mode
Picture a notes list. A long press on a note should start selection mode: the title shows the selected count, a close button appears on the left and a delete button on the right. When the selection ends, the bar returns to normal. You don't need a second AppBar for this; the same bar fills its slots based on state:
class NotesPage extends StatefulWidget {
const NotesPage({super.key});
@override
State<NotesPage> createState() => _NotesPageState();
}
class _NotesPageState extends State<NotesPage> {
final List<String> _notes = List.generate(20, (i) => 'Note ${i + 1}');
final Set<String> _selected = {};
bool get _selectionMode => _selected.isNotEmpty;
void _toggle(String note) {
setState(() {
if (!_selected.remove(note)) _selected.add(note);
});
}
void _deleteSelected() {
setState(() {
_notes.removeWhere(_selected.contains);
_selected.clear();
});
}
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Scaffold(
appBar: AppBar(
backgroundColor: _selectionMode ? scheme.primaryContainer : null,
leading: _selectionMode
? IconButton(
icon: const Icon(Icons.close),
tooltip: 'Clear selection',
onPressed: () => setState(_selected.clear),
)
: null,
title: Text(_selectionMode ? '${_selected.length} selected' : 'My Notes'),
actions: _selectionMode
? [
IconButton(
icon: const Icon(Icons.delete_outline),
tooltip: 'Delete',
onPressed: _deleteSelected,
),
]
: [
IconButton(
icon: const Icon(Icons.search),
tooltip: 'Search',
onPressed: () {},
),
],
),
body: ListView.builder(
itemCount: _notes.length,
itemBuilder: (context, index) {
final note = _notes[index];
return ListTile(
title: Text(note),
selected: _selected.contains(note),
onLongPress: () => _toggle(note),
onTap: _selectionMode ? () => _toggle(note) : null,
);
},
),
);
}
}Two details matter here. leading stays null outside selection mode, so when this page is opened through Navigator the back arrow keeps appearing on its own. backgroundColor works the same way: null falls back to the theme value. The user reads the mode change from the color, and you are not maintaining two separate bars.
Frequently Asked Questions
How do I change the height of an AppBar?
Use the toolbarHeight parameter. If you also use bottom, its height is added on top of that.
How do I remove the back button?
Set automaticallyImplyLeading: false. That only hides the button; the Android system back gesture still works, and blocking that requires PopScope.
How do I apply the same AppBar style to every screen?
Give ThemeData an AppBarTheme through its appBarTheme field. Colors, centerTitle, scrolledUnderElevation and similar settings then apply to every AppBar, and a parameter set on a single AppBar still overrides the theme.
What is the difference between AppBar and SliverAppBar?
AppBar has a fixed height and is handed to a Scaffold. SliverAppBar lives inside a CustomScrollView and can expand, collapse, hide and return as the user scrolls.
Related Posts
Flutter: Card Widget and Design Usage
Flutter Card guide: Material 3 Card.filled and Card.outlined, clipBehavior with images, tappable cards with InkWell, common mistakes and a product card.
Flutter: BottomSheet and showModalBottomSheet
Flutter's showModalBottomSheet: returning values, isScrollControlled, useSafeArea, showDragHandle, keyboard insets, DraggableScrollableSheet, persistent sheets.
Flutter: BottomNavigationBar vs NavigationBar
Migrating from BottomNavigationBar to Material 3 NavigationBar: property mapping, labelBehavior, a decision guide and an adaptive shell example.