image

11 May 2026

9K

35K

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 crucial for any e-commerce application. It's where potential customers gather information, compare options, and ultimately make purchasing decisions. In Flutter, building a dynamic and feature-rich PDP involves combining various UI components and managing state effectively. This article will guide you through creating a sophisticated PDP widget that includes essential elements like promo badges, a review carousel, related items, and a quick buy functionality.

Understanding the Core Components

Before diving into the code, let's break down the key features we aim to implement:

  1. Product Information: Displaying images, title, price, description, and quantity selector.
  2. Promo Badges: Visually highlighting special offers (e.g., "Sale," "New," "Limited Stock").
  3. Review Carousel: Showcasing customer feedback in an engaging, scrollable format.
  4. Related Items: Suggesting other products that might interest the user, increasing discoverability and sales.
  5. Quick Buy/Add to Cart: A prominent call-to-action button for immediate purchase or adding to the shopping cart.

Data Models

To manage product and review data, we'll start with simple data models. These can be extended with more fields as needed.


class Product {
  final String id;
  final String name;
  final String description;
  final double price;
  final double? originalPrice; // For sale items
  final List<String> imageUrls;
  final double averageRating;
  final int reviewCount;
  final List<String> promoBadges; // e.g., "Sale", "New", "Limited Stock"
  final List<Product> relatedProducts;

  Product({
    required this.id,
    required this.name,
    required this.description,
    required this.price,
    this.originalPrice,
    required this.imageUrls,
    required this.averageRating,
    required this.reviewCount,
    this.promoBadges = const [],
    this.relatedProducts = const [],
  });
}

class Review {
  final String reviewerName;
  final double rating;
  final String comment;
  final DateTime date;

  Review({
    required this.reviewerName,
    required this.rating,
    required this.comment,
    required this.date,
  });
}

Structuring the Product Detail Page Widget

A typical Flutter PDP will use a Scaffold with a SingleChildScrollView to ensure all content is scrollable. Inside, we'll use Column and Row widgets to arrange the various sections.


import 'package:flutter/material.dart';

// Assume Product and Review models are defined as above

class ProductDetailPage extends StatefulWidget {
  final Product product;
  final List<Review> reviews; // Can be fetched dynamically

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

  @override
  _ProductDetailPageState createState() => _ProductDetailPageState();
}

class _ProductDetailPageState extends State<ProductDetailPage> {
  int _currentImageIndex = 0;
  int _selectedQuantity = 1;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.product.name),
        actions: [
          IconButton(icon: Icon(Icons.share), onPressed: () {}),
          IconButton(icon: Icon(Icons.favorite_border), onPressed: () {}),
        ],
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            _buildProductImagesAndBadges(),
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  _buildProductTitleAndPrice(),
                  SizedBox(height: 10),
                  _buildRatingAndReviewSummary(),
                  SizedBox(height: 15),
                  _buildQuantitySelector(),
                  SizedBox(height: 15),
                  Text('Description', style: Theme.of(context).textTheme.headline6),
                  SizedBox(height: 8),
                  Text(widget.product.description),
                ],
              ),
            ),
            _buildReviewCarousel(),
            _buildRelatedItemsSection(),
          ],
        ),
      ),
      bottomNavigationBar: _buildQuickBuyButton(),
    );
  }

  // Helper methods for building sections will go here
}

1. Product Images and Promo Badges

We'll use a Stack to overlay promo badges on top of the product image. A PageView.builder can be used for multiple product images, allowing users to swipe through them.


  Widget _buildProductImagesAndBadges() {
    return Container(
      height: 300,
      color: Colors.grey[200],
      child: Stack(
        children: [
          PageView.builder(
            itemCount: widget.product.imageUrls.length,
            onPageChanged: (index) {
              setState(() {
                _currentImageIndex = index;
              });
            },
            itemBuilder: (context, index) {
              return Image.network(
                widget.product.imageUrls[index],
                fit: BoxFit.cover,
                width: double.infinity,
              );
            },
          ),
          if (widget.product.promoBadges.isNotEmpty)
            Positioned(
              top: 10,
              left: 10,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: widget.product.promoBadges.map((badge) {
                  Color badgeColor = Colors.red;
                  if (badge == "New") badgeColor = Colors.blue;
                  if (badge == "Limited Stock") badgeColor = Colors.orange;

                  return Container(
                    margin: const EdgeInsets.only(bottom: 4),
                    padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                    decoration: BoxDecoration(
                      color: badgeColor,
                      borderRadius: BorderRadius.circular(4),
                    ),
                    child: Text(
                      badge,
                      style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
                    ),
                  );
                }).toList(),
              ),
            ),
          Positioned(
            bottom: 10,
            left: 0,
            right: 0,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: List.generate(widget.product.imageUrls.length, (index) {
                return Container(
                  width: 8.0,
                  height: 8.0,
                  margin: EdgeInsets.symmetric(horizontal: 4.0),
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    color: _currentImageIndex == index ? Colors.blue : Colors.grey,
                  ),
                );
              }),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildProductTitleAndPrice() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          widget.product.name,
          style: Theme.of(context).textTheme.headline5!.copyWith(fontWeight: FontWeight.bold),
        ),
        SizedBox(height: 8),
        Row(
          children: [
            Text(
              '\$${widget.product.price.toStringAsFixed(2)}',
              style: Theme.of(context).textTheme.headline6!.copyWith(color: Colors.green, fontWeight: FontWeight.bold),
            ),
            if (widget.product.originalPrice != null) ...[
              SizedBox(width: 8),
              Text(
                '\$${widget.product.originalPrice!.toStringAsFixed(2)}',
                style: TextStyle(
                  decoration: TextDecoration.lineThrough,
                  color: Colors.grey,
                  fontSize: 16,
                ),
              ),
            ],
          ],
        ),
      ],
    );
  }

  Widget _buildRatingAndReviewSummary() {
    return Row(
      children: [
        Icon(Icons.star, color: Colors.amber, size: 20),
        SizedBox(width: 4),
        Text(
          widget.product.averageRating.toStringAsFixed(1),
          style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
        ),
        SizedBox(width: 8),
        Text(
          '(${widget.product.reviewCount} Reviews)',
          style: TextStyle(fontSize: 14, color: Colors.grey),
        ),
        Spacer(),
        TextButton(
          onPressed: () { /* Navigate to all reviews page */ },
          child: Text('See All Reviews'),
        ),
      ],
    );
  }

  Widget _buildQuantitySelector() {
    return Row(
      children: [
        Text('Quantity:', style: Theme.of(context).textTheme.subtitle1),
        SizedBox(width: 10),
        Container(
          decoration: BoxDecoration(
            border: Border.all(color: Colors.grey),
            borderRadius: BorderRadius.circular(5),
          ),
          child: Row(
            children: [
              IconButton(
                icon: Icon(Icons.remove, size: 20),
                onPressed: _selectedQuantity > 1
                    ? () {
                        setState(() {
                          _selectedQuantity--;
                        });
                      }
                    : null,
              ),
              Text(
                _selectedQuantity.toString(),
                style: TextStyle(fontSize: 16),
              ),
              IconButton(
                icon: Icon(Icons.add, size: 20),
                onPressed: () {
                  setState(() {
                    _selectedQuantity++;
                  });
                },
              ),
            ],
          ),
        ),
      ],
    );
  }

2. Review Carousel

A horizontal ListView.builder or PageView.builder is perfect for displaying a carousel of reviews. We'll show a few key details for each review.


  Widget _buildReviewCarousel() {
    if (widget.reviews.isEmpty) {
      return Padding(
        padding: const EdgeInsets.all(16.0),
        child: Text('No reviews yet. Be the first!', style: TextStyle(fontStyle: FontStyle.italic)),
      );
    }
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
          child: Text('Customer Reviews', style: Theme.of(context).textTheme.headline6),
        ),
        Container(
          height: 180, // Height for the review cards
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            itemCount: widget.reviews.length,
            itemBuilder: (context, index) {
              final review = widget.reviews[index];
              return Container(
                width: MediaQuery.of(context).size.width * 0.8, // Each card takes 80% of screen width
                margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
                child: Card(
                  elevation: 2,
                  child: Padding(
                    padding: const EdgeInsets.all(12.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          children: List.generate(5, (starIndex) {
                            return Icon(
                              starIndex < review.rating ? Icons.star : Icons.star_border,
                              color: Colors.amber,
                              size: 18,
                            );
                          }),
                        ),
                        SizedBox(height: 8),
                        Text(
                          review.comment,
                          maxLines: 3,
                          overflow: TextOverflow.ellipsis,
                          style: TextStyle(fontSize: 14),
                        ),
                        SizedBox(height: 8),
                        Text(
                          '- ${review.reviewerName}',
                          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
                        ),
                        Text(
                          review.date.toLocal().toString().split(' ')[0], // Format date
                          style: TextStyle(color: Colors.grey, fontSize: 12),
                        ),
                      ],
                    ),
                  ),
                ),
              );
            },
          ),
        ),
        SizedBox(height: 16),
      ],
    );
  }

3. Related Items Section

Similar to the review carousel, a horizontal ListView.builder is excellent for displaying a row of related products. Each product can be a smaller card that leads to its own PDP.


  Widget _buildRelatedItemsSection() {
    if (widget.product.relatedProducts.isEmpty) {
      return const SizedBox.shrink(); // Hide if no related products
    }
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
          child: Text('You Might Also Like', style: Theme.of(context).textTheme.headline6),
        ),
        Container(
          height: 200, // Height for the related product cards
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            itemCount: widget.product.relatedProducts.length,
            itemBuilder: (context, index) {
              final relatedProduct = widget.product.relatedProducts[index];
              return GestureDetector(
                onTap: () {
                  // Navigate to the related product's detail page
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => ProductDetailPage(
                        product: relatedProduct,
                        reviews: [], // Fetch related product reviews
                      ),
                    ),
                  );
                },
                child: Container(
                  width: 150,
                  margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
                  child: Card(
                    elevation: 2,
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Expanded(
                          child: Image.network(
                            relatedProduct.imageUrls.first,
                            fit: BoxFit.cover,
                            width: double.infinity,
                          ),
                        ),
                        Padding(
                          padding: const EdgeInsets.all(8.0),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                relatedProduct.name,
                                maxLines: 1,
                                overflow: TextOverflow.ellipsis,
                                style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
                              ),
                              SizedBox(height: 4),
                              Text(
                                '\$${relatedProduct.price.toStringAsFixed(2)}',
                                style: TextStyle(color: Colors.green, fontWeight: FontWeight.bold),
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              );
            },
          ),
        ),
        SizedBox(height: 16),
      ],
    );
  }

4. Quick Buy / Add to Cart Button

The call-to-action button should be prominent and easily accessible, often fixed at the bottom of the screen using a BottomNavigationBar or bottomNavigationBar property of Scaffold.


  Widget _buildQuickBuyButton() {
    return Container(
      padding: const EdgeInsets.all(16.0),
      decoration: BoxDecoration(
        color: Theme.of(context).cardColor,
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.1),
            blurRadius: 10,
            offset: Offset(0, -5),
          ),
        ],
      ),
      child: Row(
        children: [
          Expanded(
            child: ElevatedButton.icon(
              icon: Icon(Icons.shopping_cart),
              label: Text('Add to Cart', style: TextStyle(fontSize: 16)),
              onPressed: () {
                // Implement add to cart logic here
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('${_selectedQuantity}x ${widget.product.name} added to cart!')),
                );
              },
              style: ElevatedButton.styleFrom(
                primary: Colors.blue, // Button color
                onPrimary: Colors.white, // Text color
                padding: EdgeInsets.symmetric(vertical: 12),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(8),
                ),
              ),
            ),
          ),
          SizedBox(width: 10),
          Expanded(
            child: ElevatedButton(
              child: Text('Buy Now', style: TextStyle(fontSize: 16)),
              onPressed: () {
                // Implement quick buy logic here (e.g., navigate to checkout)
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('Proceeding to buy ${_selectedQuantity}x ${widget.product.name}')),
                );
              },
              style: ElevatedButton.styleFrom(
                primary: Colors.green, // Button color
                onPrimary: Colors.white, // Text color
                padding: EdgeInsets.symmetric(vertical: 12),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(8),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

Putting It All Together (Example Usage)

To see this PDP in action, you can create some dummy data and push this widget onto the navigation stack.


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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Dummy Data for demonstration
    final dummyReviews = [
      Review(
          reviewerName: 'Alice',
          rating: 4.5,
          comment: 'Great product, exactly as described!',
          date: DateTime(2023, 10, 26)),
      Review(
          reviewerName: 'Bob',
          rating: 5.0,
          comment: 'Fast shipping and excellent quality. Highly recommended.',
          date: DateTime(2023, 10, 20)),
      Review(
          reviewerName: 'Charlie',
          rating: 3.0,
          comment: 'It\'s okay, but I expected more for the price.',
          date: DateTime(2023, 09, 15)),
    ];

    final dummyRelatedProduct1 = Product(
      id: 'rp1',
      name: 'Smart Watch X',
      description: 'The next generation smart watch with health tracking.',
      price: 199.99,
      imageUrls: ['https://via.placeholder.com/300/CCCCCC/FFFFFF?text=SmartWatch'],
      averageRating: 4.2,
      reviewCount: 75,
      promoBadges: ['New'],
    );

    final dummyRelatedProduct2 = Product(
      id: 'rp2',
      name: 'Wireless Earbuds Pro',
      description: 'Noise-cancelling earbuds for an immersive audio experience.',
      price: 129.00,
      imageUrls: ['https://via.placeholder.com/300/999999/FFFFFF?text=Earbuds'],
      averageRating: 4.8,
      reviewCount: 120,
      promoBadges: ['Sale'],
    );

    final dummyProduct = Product(
      id: 'p1',
      name: 'Premium Bluetooth Speaker',
      description:
          'Experience high-fidelity audio with deep bass and crystal-clear highs. ' +
          'Perfect for home or outdoor use with its long-lasting battery and durable design.',
      price: 89.99,
      originalPrice: 120.00,
      imageUrls: [
        'https://via.placeholder.com/400/FF0000/FFFFFF?text=Speaker+Front',
        'https://via.placeholder.com/400/00FF00/FFFFFF?text=Speaker+Side',
        'https://via.placeholder.com/400/0000FF/FFFFFF?text=Speaker+Back',
      ],
      averageRating: 4.5,
      reviewCount: 50,
      promoBadges: ['Sale', 'Limited Stock'],
      relatedProducts: [dummyRelatedProduct1, dummyRelatedProduct2],
    );

    return MaterialApp(
      title: 'Flutter Product App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: ProductDetailPage(
        product: dummyProduct,
        reviews: dummyReviews,
      ),
    );
  }
}

Conclusion

Building a comprehensive Product Detail Page in Flutter involves careful consideration of layout, data presentation, and user interaction. By leveraging widgets like Stack for badges, PageView.builder or ListView.builder for carousels, and managing state for quantities, you can create a highly engaging and effective PDP. Remember to adapt the styling and functionality to match your application's specific design language and business logic, especially for state management and backend integration.

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