image

14 Dec 2025

9K

35K

Creating a Swipe-to-Delete List in Flutter

Building interactive user interfaces is a cornerstone of modern mobile application development. A common and highly intuitive UI pattern is the "swipe-to-delete" list, allowing users to remove items with a simple gesture. Flutter provides a powerful widget, Dismissible, that makes implementing this feature straightforward and efficient. This article will guide you through creating a professional swipe-to-delete list in Flutter, complete with visual feedback and state management.

Understanding the Core: The Dismissible Widget

The Dismissible widget is designed to be wrapped around another widget that you want to be able to dismiss. When a user swipes the wrapped widget, Dismissible handles the animation and provides callbacks for when the item is being dismissed and when it has been fully dismissed.

Key properties of Dismissible include:

  • key: A unique key is absolutely crucial for Dismissible to correctly identify and manage the state of each item in a list. Without a unique key, Flutter might not be able to differentiate items, leading to unexpected behavior.
  • child: The widget that the user can dismiss (e.g., a ListTile).
  • background: The widget shown behind the child when it's being swiped. This is typically used for visual feedback, like a delete icon and a red background.
  • secondaryBackground: An optional widget shown when the child is swiped in the opposite direction from the background.
  • onDismissed: A callback function that is invoked after the item has been completely dismissed. This is where you'll typically remove the item from your data source.
  • direction: Specifies the direction(s) in which the widget can be dismissed (e.g., horizontal, start-to-end, end-to-start).

Basic Implementation with ListView.builder

Let's start by creating a simple list of items using ListView.builder and wrapping each item with a Dismissible widget.


import 'package:flutter/material.dart';

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

  @override
  State createState() => _SwipeToDeleteListState();
}

class _SwipeToDeleteListState extends State {
  final List _items = List.generate(20, (i) => 'Item ${i + 1}');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Swipe to Delete List'),
      ),
      body: ListView.builder(
        itemCount: _items.length,
        itemBuilder: (context, index) {
          final String item = _items[index];
          return Dismissible(
            key: ValueKey(item), // Crucial for Dismissible!
            onDismissed: (direction) {
              setState(() {
                _items.removeAt(index);
              });
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('"$item" dismissed')),
              );
            },
            child: ListTile(
              title: Text(item),
            ),
          );
        },
      ),
    );
  }
}

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Swipe to Delete Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const SwipeToDeleteList(),
    );
  }
}

In this basic example:

  • We use a StatefulWidget to manage our list of items (_items).
  • ValueKey<String>(item) is used for the key. It's essential that this key uniquely identifies each item even after reordering or removal.
  • Inside onDismissed, we call setState to remove the item from our _items list, triggering a UI rebuild to reflect the change. A SnackBar provides temporary feedback.

Adding Visual Feedback with Backgrounds

A good swipe-to-delete experience provides clear visual cues. Typically, a red background with a delete icon appears as the item is swiped. We can achieve this using the background and secondaryBackground properties.


import 'package:flutter/material.dart';

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

  @override
  State createState() => _SwipeToDeleteListWithFeedbackState();
}

class _SwipeToDeleteListWithFeedbackState extends State {
  final List _items = List.generate(20, (i) => 'Item ${i + 1}');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Swipe to Delete with Feedback'),
      ),
      body: ListView.builder(
        itemCount: _items.length,
        itemBuilder: (context, index) {
          final String item = _items[index];
          return Dismissible(
            key: ValueKey(item),
            background: Container(
              color: Colors.red,
              alignment: Alignment.centerLeft,
              padding: const EdgeInsets.only(left: 20.0),
              child: const Icon(Icons.delete, color: Colors.white),
            ),
            secondaryBackground: Container(
              color: Colors.red,
              alignment: Alignment.centerRight,
              padding: const EdgeInsets.only(right: 20.0),
              child: const Icon(Icons.delete, color: Colors.white),
            ),
            onDismissed: (direction) {
              // Show a SnackBar with an option to undo (optional)
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('"$item" deleted'),
                  action: SnackBarAction(
                    label: 'UNDO',
                    onPressed: () {
                      setState(() {
                        // Re-insert the item at its original position
                        _items.insert(index, item);
                      });
                    },
                  ),
                ),
              );

              // Remove the item from the data source
              setState(() {
                _items.removeAt(index);
              });
            },
            child: ListTile(
              title: Text(item),
              subtitle: Text('Swipe me to delete!'),
            ),
          );
        },
      ),
    );
  }
}

void main() {
  runApp(const MyAppFeedback());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Swipe to Delete Feedback Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const SwipeToDeleteListWithFeedback(),
    );
  }
}

In this enhanced example:

  • background: A Container with a red color and a delete icon aligned to the left is shown when swiping from left to right.
  • secondaryBackground: Similar to background but with the icon aligned to the right, appearing when swiping from right to left. This creates a consistent visual cue regardless of the swipe direction.
  • We've also added a more robust SnackBar with an "UNDO" action, which demonstrates how you can temporarily remove an item and then re-add it if the user changes their mind. This improves the user experience significantly.

Advanced Considerations

  • Undo Functionality: As shown in the previous example, providing an undo option immediately after dismissal is a great way to prevent accidental deletions and improve user satisfaction.
  • Confirmation Dialogs: For critical data, you might want to show a confirmation dialog (e.g., an AlertDialog) before permanently deleting an item. You can achieve this by using the confirmDismiss callback of the Dismissible widget, which returns a Future<bool>. If the future resolves to false, the item will not be dismissed.
  • Dismiss Direction: The direction property allows you to control which swipe directions are permitted (e.g., DismissDirection.endToStart for only right-to-left swipes).
  • Thresholds: You can customize the swipe distance required for dismissal using dismissThresholds and the animation duration with movementDuration.

Conclusion

The Dismissible widget in Flutter provides a highly flexible and easy-to-use solution for creating swipe-to-delete lists. By combining it with ListView.builder and proper state management, you can build dynamic and interactive UIs that enhance user experience. Remember the importance of unique keys, clear visual feedback, and considering undo functionality for a truly professional implementation.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is