image

27 Jan 2026

9K

35K

Building a Swipe-to-Remove Shopping Cart Widget in Flutter

Enhancing user experience in e-commerce applications is paramount. One common interaction pattern that significantly improves usability, especially in shopping cart functionalities, is the "swipe-to-remove" gesture. This feature allows users to intuitively remove items from their cart with a simple horizontal swipe, providing immediate feedback and a streamlined workflow. This article will guide you through building a dynamic Flutter shopping cart widget that incorporates this modern interaction using the Dismissible widget.

Core Concepts

Before diving into the implementation, let's briefly touch upon the key Flutter widgets and concepts we'll be utilizing:

  • StatefulWidget: Essential for managing the mutable state of our shopping cart, such as the list of items.
  • ListView.builder: An efficient way to display a scrollable list of items, especially when the number of items can be large or dynamic.
  • Dismissible: The cornerstone of our swipe-to-remove functionality. It allows a widget to be removed from the tree with a swipe gesture.
  • State Management (Implicit): For this example, we'll use a local setState call. In larger applications, consider state management solutions like Provider, BLoC, or Riverpod.

Step 1: Define the Cart Item Data Model

First, we need a simple data model for our cart items. This class will hold properties like name, price, and a unique ID.


class CartItem {
  final String id;
  final String name;
  final double price;
  int quantity;

  CartItem({
    required this.id,
    required this.name,
    required this.price,
    this.quantity = 1,
  });

  // For simplicity, we'll use copyWith for quantity updates
  CartItem copyWith({
    String? id,
    String? name,
    double? price,
    int? quantity,
  }) {
    return CartItem(
      id: id ?? this.id,
      name: name ?? this.name,
      price: price ?? this.price,
      quantity: quantity ?? this.quantity,
    );
  }
}

Step 2: Create the Shopping Cart Widget Structure

We'll build a StatefulWidget that maintains a list of CartItem objects. This widget will use ListView.builder to display each item.


import 'package:flutter/material.dart';

// (CartItem class definition from Step 1 goes here)

class ShoppingCartScreen extends StatefulWidget {
  @override
  _ShoppingCartScreenState createState() => _ShoppingCartScreenState();
}

class _ShoppingCartScreenState extends State {
  List<CartItem> _cartItems = [
    CartItem(id: 'p1', name: 'Flutter T-Shirt', price: 25.00),
    CartItem(id: 'p2', name: 'Dart Mug', price: 12.50, quantity: 2),
    CartItem(id: 'p3', name: 'Flutter Sticker Pack', price: 5.00),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('My Shopping Cart'),
      ),
      body: _cartItems.isEmpty
          ? Center(
              child: Text(
                'Your cart is empty!',
                style: TextStyle(fontSize: 18),
              ),
            )
          : ListView.builder(
              itemCount: _cartItems.length,
              itemBuilder: (context, index) {
                final item = _cartItems[index];
                return Card(
                  margin: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
                  child: Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: ListTile(
                      title: Text(item.name),
                      subtitle: Text(
                        'Price: \$${item.price.toStringAsFixed(2)} x ${item.quantity}',
                      ),
                      trailing: Text(
                        '\$${(item.price * item.quantity).toStringAsFixed(2)}',
                        style: TextStyle(
                            fontWeight: FontWeight.bold, fontSize: 16),
                      ),
                    ),
                  ),
                );
              },
            ),
      bottomNavigationBar: _buildTotalBar(),
    );
  }

  Widget _buildTotalBar() {
    final total = _cartItems.fold(
      0.0,
      (sum, item) => sum + (item.price * item.quantity),
    );
    return Container(
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Theme.of(context).primaryColor,
        boxShadow: [
          BoxShadow(
            color: Colors.black26,
            blurRadius: 5,
            offset: Offset(0, -3),
          ),
        ],
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(
            'Total:',
            style: TextStyle(
              color: Colors.white,
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
          Text(
            '\$${total.toStringAsFixed(2)}',
            style: TextStyle(
              color: Colors.white,
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }
}

Step 3: Implementing Swipe-to-Remove with Dismissible

Now, let's integrate the Dismissible widget. We'll wrap each Card item with Dismissible, providing a unique key, a background visual, and an onDismissed callback.


// (Previous imports and CartItem class)

class ShoppingCartScreen extends StatefulWidget {
  @override
  _ShoppingCartScreenState createState() => _ShoppingCartScreenState();
}

class _ShoppingCartScreenState extends State<ShoppingCartScreen> {
  List<CartItem> _cartItems = [
    CartItem(id: 'p1', name: 'Flutter T-Shirt', price: 25.00),
    CartItem(id: 'p2', name: 'Dart Mug', price: 12.50, quantity: 2),
    CartItem(id: 'p3', name: 'Flutter Sticker Pack', price: 5.00),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('My Shopping Cart'),
      ),
      body: _cartItems.isEmpty
          ? Center(
              child: Text(
                'Your cart is empty!',
                style: TextStyle(fontSize: 18),
              ),
            )
          : ListView.builder(
              itemCount: _cartItems.length,
              itemBuilder: (context, index) {
                final item = _cartItems[index];
                return Dismissible(
                  key: ValueKey(item.id), // Unique key is crucial for Dismissible
                  direction: DismissDirection.endToStart, // Only swipe from right to left
                  background: Container(
                    color: Colors.red,
                    alignment: Alignment.centerRight,
                    padding: EdgeInsets.symmetric(horizontal: 20),
                    child: Icon(
                      Icons.delete,
                      color: Colors.white,
                      size: 30,
                    ),
                  ),
                  onDismissed: (direction) {
                    // Store the item temporarily for undo functionality
                    final removedItem = item;
                    final removedIndex = index;

                    setState(() {
                      _cartItems.removeAt(index);
                    });

                    // Show a SnackBar with an undo option
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('${removedItem.name} removed from cart.'),
                        action: SnackBarAction(
                          label: 'UNDO',
                          onPressed: () {
                            setState(() {
                              _cartItems.insert(removedIndex, removedItem);
                            });
                          },
                        ),
                      ),
                    );
                  },
                  child: Card(
                    margin: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: ListTile(
                        title: Text(item.name),
                        subtitle: Text(
                          'Price: \$${item.price.toStringAsFixed(2)} x ${item.quantity}',
                        ),
                        trailing: Text(
                          '\$${(item.price * item.quantity).toStringAsFixed(2)}',
                          style: TextStyle(
                              fontWeight: FontWeight.bold, fontSize: 16),
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
      bottomNavigationBar: _buildTotalBar(),
    );
  }

  // (Remaining _buildTotalBar() method from Step 2)
  Widget _buildTotalBar() {
    final total = _cartItems.fold(
      0.0,
      (sum, item) => sum + (item.price * item.quantity),
    );
    return Container(
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Theme.of(context).primaryColor,
        boxShadow: [
          BoxShadow(
            color: Colors.black26,
            blurRadius: 5,
            offset: Offset(0, -3),
          ),
        ],
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(
            'Total:',
            style: TextStyle(
              color: Colors.white,
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
          Text(
            '\$${total.toStringAsFixed(2)}',
            style: TextStyle(
              color: Colors.white,
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }
}

Full Code Example

Here is the complete code for a basic Flutter application demonstrating the swipe-to-remove shopping cart widget.


import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Shopping Cart Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: ShoppingCartScreen(),
    );
  }
}

class CartItem {
  final String id;
  final String name;
  final double price;
  int quantity;

  CartItem({
    required this.id,
    required this.name,
    required this.price,
    this.quantity = 1,
  });

  CartItem copyWith({
    String? id,
    String? name,
    double? price,
    int? quantity,
  }) {
    return CartItem(
      id: id ?? this.id,
      name: name ?? this.name,
      price: price ?? this.price,
      quantity: quantity ?? this.quantity,
    );
  }
}

class ShoppingCartScreen extends StatefulWidget {
  @override
  _ShoppingCartScreenState createState() => _ShoppingCartScreenState();
}

class _ShoppingCartScreenState extends State<ShoppingCartScreen> {
  List<CartItem> _cartItems = [
    CartItem(id: 'p1', name: 'Flutter T-Shirt', price: 25.00),
    CartItem(id: 'p2', name: 'Dart Mug', price: 12.50, quantity: 2),
    CartItem(id: 'p3', name: 'Flutter Sticker Pack', price: 5.00),
    CartItem(id: 'p4', name: 'Flutter Hoodie', price: 49.99, quantity: 1),
    CartItem(id: 'p5', name: 'Dart Keyboard Keycap', price: 8.00, quantity: 3),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('My Shopping Cart'),
      ),
      body: _cartItems.isEmpty
          ? Center(
              child: Text(
                'Your cart is empty!',
                style: TextStyle(fontSize: 18),
              ),
            )
          : ListView.builder(
              itemCount: _cartItems.length,
              itemBuilder: (context, index) {
                final item = _cartItems[index];
                return Dismissible(
                  key: ValueKey(item.id), // Unique key for Dismissible
                  direction: DismissDirection.endToStart, // Swipe from right to left
                  background: Container(
                    color: Colors.red,
                    alignment: Alignment.centerRight,
                    padding: EdgeInsets.symmetric(horizontal: 20),
                    child: Icon(
                      Icons.delete,
                      color: Colors.white,
                      size: 30,
                    ),
                  ),
                  confirmDismiss: (direction) async {
                    return await showDialog(
                      context: context,
                      builder: (BuildContext context) {
                        return AlertDialog(
                          title: Text("Confirm Delete"),
                          content: Text(
                              "Are you sure you want to remove ${item.name} from your cart?"),
                          actions: <Widget>[
                            TextButton(
                                onPressed: () => Navigator.of(context).pop(false),
                                child: Text("CANCEL")),
                            TextButton(
                                onPressed: () => Navigator.of(context).pop(true),
                                child: Text("DELETE")),
                          ],
                        );
                      },
                    );
                  },
                  onDismissed: (direction) {
                    final removedItem = item;
                    final removedIndex = index;

                    setState(() {
                      _cartItems.removeAt(index);
                    });

                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(
                        content: Text('${removedItem.name} removed.'),
                        action: SnackBarAction(
                          label: 'UNDO',
                          onPressed: () {
                            setState(() {
                              _cartItems.insert(removedIndex, removedItem);
                            });
                          },
                        ),
                      ),
                    );
                  },
                  child: Card(
                    margin: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
                    elevation: 2,
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: ListTile(
                        leading: CircleAvatar(
                          backgroundColor: Theme.of(context).primaryColorLight,
                          child: Text(
                            item.name[0],
                            style: TextStyle(color: Theme.of(context).primaryColor),
                          ),
                        ),
                        title: Text(
                          item.name,
                          style: TextStyle(fontWeight: FontWeight.bold),
                        ),
                        subtitle: Text(
                          'Price: \$${item.price.toStringAsFixed(2)}',
                        ),
                        trailing: Row(
                          mainAxisSize: MainAxisSize.min,
                          children: [
                            IconButton(
                              icon: Icon(Icons.remove_circle_outline),
                              onPressed: () {
                                setState(() {
                                  if (item.quantity > 1) {
                                    item.quantity--;
                                  } else {
                                    // Optionally remove item if quantity becomes 0
                                    _cartItems.removeAt(index);
                                    ScaffoldMessenger.of(context).showSnackBar(
                                      SnackBar(
                                        content: Text('${item.name} removed.'),
                                      ),
                                    );
                                  }
                                });
                              },
                            ),
                            Text('${item.quantity}'),
                            IconButton(
                              icon: Icon(Icons.add_circle_outline),
                              onPressed: () {
                                setState(() {
                                  item.quantity++;
                                });
                              },
                            ),
                            SizedBox(width: 10),
                            Text(
                              '\$${(item.price * item.quantity).toStringAsFixed(2)}',
                              style: TextStyle(
                                  fontWeight: FontWeight.bold, fontSize: 16),
                            ),
                          ],
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
      bottomNavigationBar: _buildTotalBar(),
    );
  }

  Widget _buildTotalBar() {
    final total = _cartItems.fold(
      0.0,
      (sum, item) => sum + (item.price * item.quantity),
    );
    return Container(
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Theme.of(context).primaryColor,
        boxShadow: [
          BoxShadow(
            color: Colors.black26,
            blurRadius: 5,
            offset: Offset(0, -3),
          ),
        ],
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(
            'Total:',
            style: TextStyle(
              color: Colors.white,
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
          Text(
            '\$${total.toStringAsFixed(2)}',
            style: TextStyle(
              color: Colors.white,
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }
}

Conclusion

By leveraging Flutter's Dismissible widget, we can easily create intuitive and user-friendly swipe-to-remove functionality for shopping cart items. This approach not only enhances the visual appeal but also streamlines the user's interaction with the application, making the process of managing their cart more efficient and enjoyable. You can further extend this by integrating robust state management solutions, animations, and more complex item manipulation features like changing quantities directly within the list item.

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