AlertDialog, SnackBar & BottomSheet
Dialogs, SnackBars & Bottom Sheets
Flutter provides several built-in widgets for showing temporary messages, confirmation prompts, and action panels. In this lesson you will learn how to use AlertDialog, SimpleDialog, SnackBar, and BottomSheet to communicate with users effectively. These widgets are essential for every production app because they give users feedback, request confirmation, and present contextual actions.
Future that resolves when the overlay is dismissed. This means you can await the result to know which action the user chose.showDialog & AlertDialog
The showDialog function displays a modal dialog above the current content. The most common dialog widget is AlertDialog, which provides a title, content area, and action buttons.
Basic AlertDialog
void _showBasicAlert(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext ctx) {
return AlertDialog(
title: const Text('Delete Item'),
content: const Text(
'Are you sure you want to delete this item? '
'This action cannot be undone.',
),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(ctx).pop(true),
child: const Text('Delete'),
),
],
);
},
);
}
Handling the Dialog Result
Since showDialog returns a Future, you can await the result passed to Navigator.pop.
Awaiting Dialog Result
Future<void> _confirmDelete(BuildContext context) async {
final bool? confirmed = await showDialog<bool>(
context: context,
barrierDismissible: false, // user must tap a button
builder: (BuildContext ctx) {
return AlertDialog(
title: const Text('Confirm Deletion'),
content: const Text('This will permanently remove the file.'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () => Navigator.of(ctx).pop(true),
style: FilledButton.styleFrom(
backgroundColor: Colors.red,
),
child: const Text('Delete'),
),
],
);
},
);
if (confirmed == true) {
// Perform the deletion
debugPrint('Item deleted');
}
}
barrierDismissible: false when the user must explicitly choose an action. By default the dialog closes when tapping outside it, which could lead to ambiguous results.Customising AlertDialog Appearance
You can customise the shape, background colour, padding, and elevation of an AlertDialog.
Styled AlertDialog
AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
backgroundColor: Colors.grey.shade50,
elevation: 8,
titlePadding: const EdgeInsets.fromLTRB(24, 24, 24, 0),
contentPadding: const EdgeInsets.fromLTRB(24, 16, 24, 0),
actionsPadding: const EdgeInsets.all(16),
title: const Text('Custom Dialog'),
content: const Text('This dialog has custom styling.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
)
SimpleDialog
When you need the user to choose from a list of options rather than confirm or cancel, use SimpleDialog. Each option is a SimpleDialogOption.
SimpleDialog Example
Future<void> _chooseLanguage(BuildContext context) async {
final String? selected = await showDialog<String>(
context: context,
builder: (BuildContext ctx) {
return SimpleDialog(
title: const Text('Choose Language'),
children: [
SimpleDialogOption(
onPressed: () => Navigator.pop(ctx, 'en'),
child: const Text('English'),
),
SimpleDialogOption(
onPressed: () => Navigator.pop(ctx, 'ar'),
child: const Text('Arabic'),
),
SimpleDialogOption(
onPressed: () => Navigator.pop(ctx, 'es'),
child: const Text('Spanish'),
),
],
);
},
);
if (selected != null) {
debugPrint('User chose: \$selected');
}
}
showSnackBar & SnackBar
A SnackBar is a lightweight message bar that appears at the bottom of the screen. It is ideal for brief feedback messages like "Item saved" or "Connection lost". You show a SnackBar through the ScaffoldMessenger.
Basic SnackBar
void _showSnackBar(BuildContext context) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Item saved successfully!'),
),
);
}
SnackBar with Action
SnackBars can include an action button, commonly used for undo operations.
SnackBar with Undo Action
void _deleteWithUndo(BuildContext context, String itemName) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('\$itemName deleted'),
duration: const Duration(seconds: 5),
action: SnackBarAction(
label: 'UNDO',
onPressed: () {
// Restore the deleted item
debugPrint('Undo delete of \$itemName');
},
),
),
);
}
SnackBar Behaviour & Styling
You can control where the SnackBar appears and how it looks.
Floating SnackBar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('This is a floating snackbar'),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
margin: const EdgeInsets.all(16),
backgroundColor: Colors.green.shade700,
duration: const Duration(seconds: 3),
),
);
ScaffoldMessenger.of(context) instead of the deprecated Scaffold.of(context).showSnackBar. The ScaffoldMessenger approach works even when the Scaffold is rebuilt and persists across route changes.showModalBottomSheet
A modal bottom sheet slides up from the bottom of the screen and blocks interaction with the content behind it. It is perfect for presenting a set of actions or a short form.
Basic Modal Bottom Sheet
void _showActions(BuildContext context) {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
builder: (BuildContext ctx) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(Icons.camera_alt),
title: const Text('Take Photo'),
onTap: () {
Navigator.pop(ctx);
debugPrint('Camera selected');
},
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('Choose from Gallery'),
onTap: () {
Navigator.pop(ctx);
debugPrint('Gallery selected');
},
),
ListTile(
leading: const Icon(Icons.delete),
title: const Text('Remove Photo'),
onTap: () {
Navigator.pop(ctx);
debugPrint('Remove selected');
},
),
],
),
);
},
);
}
showBottomSheet (Non-Modal)
Unlike the modal version, showBottomSheet does not block interaction with the rest of the screen. It is called from a Scaffold context.
Persistent Bottom Sheet
void _showPersistentSheet(BuildContext context) {
Scaffold.of(context).showBottomSheet(
(BuildContext ctx) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.blue.shade50,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(16),
),
),
child: const Text(
'This is a persistent bottom sheet. '
'You can still interact with the content behind.',
),
);
},
);
}
DraggableScrollableSheet
For bottom sheets that the user can drag to expand or collapse, use DraggableScrollableSheet inside a modal bottom sheet.
Draggable Bottom Sheet
void _showDraggableSheet(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext ctx) {
return DraggableScrollableSheet(
initialChildSize: 0.4,
minChildSize: 0.2,
maxChildSize: 0.9,
expand: false,
builder: (BuildContext context, ScrollController controller) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(
top: Radius.circular(20),
),
),
child: ListView.builder(
controller: controller,
itemCount: 30,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item \${index + 1}'),
);
},
),
);
},
);
},
);
}
isScrollControlled: true on the modal bottom sheet when using DraggableScrollableSheet. Without it the sheet height is limited to half the screen.Practical Example: Confirm Delete Dialog
Combining an AlertDialog with a SnackBar gives a complete user experience for destructive actions.
Complete Delete Flow
class ItemListScreen extends StatefulWidget {
const ItemListScreen({super.key});
@override
State<ItemListScreen> createState() => _ItemListScreenState();
}
class _ItemListScreenState extends State<ItemListScreen> {
final List<String> _items = [
'Document A',
'Document B',
'Document C',
];
Future<void> _handleDelete(int index) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Delete Document'),
content: Text(
'Delete "\${_items[index]}"? This cannot be undone.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
style: FilledButton.styleFrom(
backgroundColor: Colors.red,
),
child: const Text('Delete'),
),
],
),
);
if (confirmed == true) {
final removedItem = _items[index];
setState(() => _items.removeAt(index));
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('\$removedItem deleted'),
action: SnackBarAction(
label: 'UNDO',
onPressed: () {
setState(() => _items.insert(index, removedItem));
},
),
),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('My Documents')),
body: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_items[index]),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _handleDelete(index),
),
);
},
),
);
}
}
Practical Example: Action Sheet
A bottom sheet that presents actions like sharing, editing, or deleting a resource.
Action Sheet with Icons
void _showActionSheet(BuildContext context) {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (ctx) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 40,
height: 4,
margin: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(2),
),
),
ListTile(
leading: const Icon(Icons.share),
title: const Text('Share'),
onTap: () => Navigator.pop(ctx),
),
ListTile(
leading: const Icon(Icons.edit),
title: const Text('Edit'),
onTap: () => Navigator.pop(ctx),
),
ListTile(
leading: const Icon(Icons.bookmark_add),
title: const Text('Bookmark'),
onTap: () => Navigator.pop(ctx),
),
const Divider(),
ListTile(
leading: const Icon(Icons.delete, color: Colors.red),
title: const Text(
'Delete',
style: TextStyle(color: Colors.red),
),
onTap: () => Navigator.pop(ctx),
),
const SizedBox(height: 8),
],
),
);
},
);
}
Summary
showDialog+AlertDialog-- modal confirmation or information dialogsSimpleDialog-- choosing from a list of optionsScaffoldMessenger.showSnackBar-- brief feedback messages at the bottomshowModalBottomSheet-- action sheets and short forms that block background interactionshowBottomSheet-- persistent sheets that allow background interactionDraggableScrollableSheet-- expandable sheets the user can resize by dragging- All overlay functions return
Futurevalues you canawait
Practice Exercise
Build a notes app screen with a list of notes. Add a floating action button that opens a bottom sheet with a text field for adding a new note. Each note item should have a delete icon that shows a confirmation AlertDialog. After deletion, show a SnackBar with an undo action that restores the note.