image

01 Feb 2026

9K

35K

Creating a Shopping Product Card Widget with a Discount Badge in Flutter

In e-commerce applications, a well-designed product card is crucial for showcasing items effectively and enticing users. A common and highly effective visual element is a discount badge, which immediately highlights special offers and can significantly boost conversion rates. This article will guide you through creating a professional and reusable Flutter widget for a shopping product card, complete with an eye-catching discount badge.

Understanding the Core Components

A typical product card consists of several key elements. For our widget, we'll focus on:

  • Product Image: The primary visual representation of the product.
  • Product Title/Description: A concise summary of the product.
  • Price Display: Showing both the original and discounted prices, with the original often struck through.
  • Discount Badge: A prominent indicator of the percentage discount.
  • Call-to-Action (e.g., Add to Cart button): While we won't fully implement the button's logic, we'll include its visual representation.

Step-by-Step Implementation

Let's break down the creation process into manageable steps.

1. Define the Product Data Model

First, we need a simple data model to represent our product's information. This makes our widget more generic and easier to populate with dynamic data.


class Product {
  final String id;
  final String name;
  final String imageUrl;
  final double originalPrice;
  final double? discountedPrice; // Optional, if no discount
  final String description;

  Product({
    required this.id,
    required this.name,
    required this.imageUrl,
    required this.originalPrice,
    this.discountedPrice,
    this.description = '',
  });

  // Calculate discount percentage
  double get discountPercentage {
    if (discountedPrice == null || discountedPrice! >= originalPrice) {
      return 0.0;
    }
    return ((originalPrice - discountedPrice!) / originalPrice) * 100;
  }
}

2. Create the Discount Badge Widget

The discount badge is a self-contained component that can be placed on top of the product image. We'll use a `Positioned` widget within a `Stack` to achieve this.


import 'package:flutter/material.dart';

class DiscountBadge extends StatelessWidget {
  final double discountPercentage;
  final Color backgroundColor;
  final Color textColor;

  const DiscountBadge({
    Key? key,
    required this.discountPercentage,
    this.backgroundColor = Colors.red,
    this.textColor = Colors.white,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    if (discountPercentage <= 0) {
      return const SizedBox.shrink(); // Don't show if no discount
    }

    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      decoration: BoxDecoration(
        color: backgroundColor,
        borderRadius: BorderRadius.circular(4),
      ),
      child: Text(
        '${discountPercentage.toStringAsFixed(0)}% OFF',
        style: TextStyle(
          color: textColor,
          fontWeight: FontWeight.bold,
          fontSize: 12,
        ),
      ),
    );
  }
}

3. Build the Product Card Widget

Now, let's assemble the main `ProductCard` widget. We'll use a `Card` for the overall structure, `Column` for vertical arrangement of details, and `Stack` to layer the image and badge.


import 'package:flutter/material.dart';
// Assuming Product and DiscountBadge are in the same project or imported correctly
// import 'product_model.dart'; 
// import 'discount_badge.dart';

class ProductCard extends StatelessWidget {
  final Product product;
  final Function(Product)? onAddToCart;

  const ProductCard({
    Key? key,
    required this.product,
    this.onAddToCart,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      margin: const EdgeInsets.all(8),
      child: InkWell(
        onTap: () {
          // Handle product tap (e.g., navigate to product detail page)
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('Tapped on ${product.name}')),
          );
        },
        borderRadius: BorderRadius.circular(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildProductImage(),
            _buildProductDetails(),
          ],
        ),
      ),
    );
  }

  Widget _buildProductImage() {
    return Stack(
      children: [
        ClipRRect(
          borderRadius: const BorderRadius.vertical(top: Radius.circular(12)),
          child: Image.network(
            product.imageUrl,
            height: 150,
            width: double.infinity,
            fit: BoxFit.cover,
            errorBuilder: (context, error, stackTrace) => Container(
              height: 150,
              width: double.infinity,
              color: Colors.grey[200],
              child: Icon(Icons.broken_image, color: Colors.grey[400]),
            ),
          ),
        ),
        if (product.discountPercentage > 0)
          Positioned(
            top: 8,
            right: 8,
            child: DiscountBadge(
              discountPercentage: product.discountPercentage,
            ),
          ),
      ],
    );
  }

  Widget _buildProductDetails() {
    return Padding(
      padding: const EdgeInsets.all(12.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            product.name,
            style: const TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.bold,
            ),
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
          ),
          const SizedBox(height: 4),
          Text(
            product.description,
            style: TextStyle(
              fontSize: 12,
              color: Colors.grey[600],
            ),
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
          const SizedBox(height: 8),
          _buildPriceSection(),
          const SizedBox(height: 8),
          Align(
            alignment: Alignment.centerRight,
            child: ElevatedButton.icon(
              onPressed: onAddToCart != null ? () => onAddToCart!(product) : null,
              icon: const Icon(Icons.add_shopping_cart, size: 18),
              label: const Text('Add to Cart'),
              style: ElevatedButton.styleFrom(
                primary: Theme.of(context).primaryColor,
                onPrimary: Colors.white,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(8),
                ),
                padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildPriceSection() {
    return Row(
      children: [
        if (product.discountedPrice != null && product.discountedPrice! < product.originalPrice)
          Text(
            '\$${product.originalPrice.toStringAsFixed(2)}',
            style: const TextStyle(
              fontSize: 14,
              color: Colors.grey,
              decoration: TextDecoration.lineThrough,
            ),
          ),
        if (product.discountedPrice != null && product.discountedPrice! < product.originalPrice)
          const SizedBox(width: 8),
        Text(
          '\$${(product.discountedPrice ?? product.originalPrice).toStringAsFixed(2)}',
          style: TextStyle(
            fontSize: (product.discountedPrice != null && product.discountedPrice! < product.originalPrice) ? 18 : 16,
            fontWeight: FontWeight.bold,
            color: (product.discountedPrice != null && product.discountedPrice! < product.originalPrice) ? Colors.green : Colors.black,
          ),
        ),
      ],
    );
  }
}

4. Usage Example

To demonstrate how to use our new `ProductCard` widget, we can place it within a `ListView` or `GridView` on a sample screen.


import 'package:flutter/material.h';
// import 'product_card.dart'; // Ensure these are imported from your files
// import 'product_model.dart';
// import 'discount_badge.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Shopping App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: ProductListingScreen(),
    );
  }
}

class ProductListingScreen extends StatelessWidget {
  final List products = [
    Product(
      id: 'p1',
      name: 'Stylish Men\'s T-Shirt',
      imageUrl: 'https://via.placeholder.com/150/FF5733/FFFFFF?text=T-Shirt',
      originalPrice: 25.00,
      discountedPrice: 19.99,
      description: 'Comfortable cotton blend, perfect for casual wear.',
    ),
    Product(
      id: 'p2',
      name: 'Wireless Bluetooth Headphones with Noise Cancelling',
      imageUrl: 'https://via.placeholder.com/150/33FF57/FFFFFF?text=Headphones',
      originalPrice: 99.99,
      discountedPrice: 74.99,
      description: 'Immersive sound experience with long-lasting battery.',
    ),
    Product(
      id: 'p3',
      name: 'Smartwatch Fitness Tracker',
      imageUrl: 'https://via.placeholder.com/150/3366FF/FFFFFF?text=Smartwatch',
      originalPrice: 120.00,
      description: 'Track your steps, heart rate, and notifications.', // No discount
    ),
    Product(
      id: 'p4',
      name: 'Ergonomic Office Chair',
      imageUrl: 'https://via.placeholder.com/150/FF33FF/FFFFFF?text=Chair',
      originalPrice: 250.00,
      discountedPrice: 150.00,
      description: 'Adjustable lumbar support for maximum comfort.',
    ),
  ];

  ProductListingScreen({Key? key}) : super(key: key);

  void _onAddToCart(Product product) {
    // Implement your add to cart logic here
    print('Added ${product.name} to cart!');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Product Listing'),
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(8),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2, // Two cards per row
          childAspectRatio: 0.7, // Adjust as needed
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
        ),
        itemCount: products.length,
        itemBuilder: (context, index) {
          return ProductCard(
            product: products[index],
            onAddToCart: _onAddToCart,
          );
        },
      ),
    );
  }
}

Conclusion

By following these steps, you've successfully created a reusable and aesthetically pleasing shopping product card widget in Flutter, complete with a dynamic discount badge. This modular approach makes your code cleaner, easier to maintain, and highly adaptable for various e-commerce interfaces. You can further enhance this widget by adding features like favorite buttons, rating stars, or more sophisticated animations.

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