flutter
swipe list
mobile development
user interface
flutter widgets

Swipe List Item for more options Flutter

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Flutter provides the built-in Dismissible widget for swipe-to-delete behavior, but for revealing multiple action buttons (like iOS-style swipe menus), you need either a custom implementation or a package like flutter_slidable. The Dismissible widget handles single-action swipe gestures natively, while flutter_slidable supports multiple actions on both sides of a list item with customizable animations.

Using the Dismissible Widget

Dismissible wraps a list item and detects horizontal swipe gestures. When swiped far enough, it removes the item:

dart
1ListView.builder(
2  itemCount: items.length,
3  itemBuilder: (context, index) {
4    return Dismissible(
5      key: Key(items[index].id),
6      onDismissed: (direction) {
7        setState(() {
8          items.removeAt(index);
9        });
10        ScaffoldMessenger.of(context).showSnackBar(
11          SnackBar(content: Text('Item deleted')),
12        );
13      },
14      background: Container(
15        color: Colors.red,
16        alignment: Alignment.centerRight,
17        padding: EdgeInsets.only(right: 16),
18        child: Icon(Icons.delete, color: Colors.white),
19      ),
20      child: ListTile(
21        title: Text(items[index].name),
22      ),
23    );
24  },
25)

Each Dismissible requires a unique key so Flutter can track which item was swiped.

Controlling Swipe Direction

dart
1Dismissible(
2  key: Key(item.id),
3  direction: DismissDirection.endToStart,  // Only swipe right-to-left
4  onDismissed: (direction) {
5    // Handle delete
6  },
7  background: Container(color: Colors.red),
8  child: ListTile(title: Text(item.name)),
9)

Available directions:

  • DismissDirection.endToStart — swipe left (most common for delete)
  • DismissDirection.startToEnd — swipe right
  • DismissDirection.horizontal — both directions (default)
  • DismissDirection.none — disable swiping

Confirm Before Dismiss

dart
1Dismissible(
2  key: Key(item.id),
3  confirmDismiss: (direction) async {
4    return await showDialog(
5      context: context,
6      builder: (context) => AlertDialog(
7        title: Text('Delete Item'),
8        content: Text('Are you sure?'),
9        actions: [
10          TextButton(
11            onPressed: () => Navigator.of(context).pop(false),
12            child: Text('Cancel'),
13          ),
14          TextButton(
15            onPressed: () => Navigator.of(context).pop(true),
16            child: Text('Delete'),
17          ),
18        ],
19      ),
20    );
21  },
22  onDismissed: (direction) {
23    setState(() => items.removeAt(index));
24  },
25  child: ListTile(title: Text(item.name)),
26)

confirmDismiss returns a Future<bool>. Returning false cancels the dismiss and snaps the item back.

Multiple Actions with flutter_slidable

For revealing action buttons without fully dismissing the item, use the flutter_slidable package:

yaml
# pubspec.yaml
dependencies:
  flutter_slidable: ^3.0.0
dart
1import 'package:flutter_slidable/flutter_slidable.dart';
2
3ListView.builder(
4  itemCount: items.length,
5  itemBuilder: (context, index) {
6    return Slidable(
7      // Left side actions (swipe right)
8      startActionPane: ActionPane(
9        motion: const ScrollMotion(),
10        children: [
11          SlidableAction(
12            onPressed: (context) => _archiveItem(index),
13            backgroundColor: Colors.blue,
14            foregroundColor: Colors.white,
15            icon: Icons.archive,
16            label: 'Archive',
17          ),
18          SlidableAction(
19            onPressed: (context) => _shareItem(index),
20            backgroundColor: Colors.green,
21            foregroundColor: Colors.white,
22            icon: Icons.share,
23            label: 'Share',
24          ),
25        ],
26      ),
27      // Right side actions (swipe left)
28      endActionPane: ActionPane(
29        motion: const StretchMotion(),
30        dismissible: DismissiblePane(onDismissed: () {
31          setState(() => items.removeAt(index));
32        }),
33        children: [
34          SlidableAction(
35            onPressed: (context) => _editItem(index),
36            backgroundColor: Colors.orange,
37            foregroundColor: Colors.white,
38            icon: Icons.edit,
39            label: 'Edit',
40          ),
41          SlidableAction(
42            onPressed: (context) => _deleteItem(index),
43            backgroundColor: Colors.red,
44            foregroundColor: Colors.white,
45            icon: Icons.delete,
46            label: 'Delete',
47          ),
48        ],
49      ),
50      child: ListTile(
51        title: Text(items[index].name),
52        subtitle: Text(items[index].description),
53      ),
54    );
55  },
56)

Slidable Motion Types

flutter_slidable supports different animation styles for the action pane:

dart
1// Actions scroll in from the side
2ActionPane(motion: const ScrollMotion(), children: [...])
3
4// Actions stretch from behind the item
5ActionPane(motion: const StretchMotion(), children: [...])
6
7// Actions slide out from behind with a parallax effect
8ActionPane(motion: const BehindMotion(), children: [...])
9
10// Actions unfold like a drawer
11ActionPane(motion: const DrawerMotion(), children: [...])

Closing Slidable Programmatically

dart
1// Close all open slidables in the group
2Slidable.of(context)?.close();
3
4// Use a SlidableController for more control
5final controller = SlidableController(this);
6
7Slidable(
8  controller: controller,
9  // ...
10)
11
12// Close from outside
13controller.close();

Common Pitfalls

  • Missing unique keys on Dismissible: Without a unique Key, Flutter cannot track which item was swiped. Using the index as a key (Key('$index')) causes bugs when items are reordered or removed — use the item's ID instead.
  • Not removing the item in onDismissed: Dismissible only handles the animation. If you do not remove the item from your data source in onDismissed, the item reappears when the list rebuilds, causing a "duplicate key" error.
  • Using Dismissible for reveal-style actions: Dismissible is designed for swipe-to-delete. It does not support revealing action buttons underneath. Use flutter_slidable for iOS-style swipe menus with multiple actions.
  • Forgetting confirmDismiss for destructive actions: Without a confirmation dialog, swiped items are deleted immediately with no undo. Always add confirmDismiss or provide a snackbar with an undo action.
  • Nesting Slidable inside scrollable widgets: Slidable gestures can conflict with horizontal PageView or TabBarView widgets. Set closeOnScroll: true (the default) and test gesture interactions on real devices.

Summary

  • Dismissible is built into Flutter for simple swipe-to-delete behavior
  • Use confirmDismiss to show a confirmation dialog before removing items
  • flutter_slidable provides multi-action swipe menus with customizable animations
  • Each swipeable item needs a unique Key for proper tracking
  • Choose motion types (ScrollMotion, StretchMotion, DrawerMotion) to match your design
  • Always remove the item from your data source in the dismiss callback

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.