image

05 Apr 2026

9K

35K

Building a Robust Product Detail Page Widget in Flutter

A well-crafted Product Detail Page (PDP) is crucial for any e-commerce application, serving as the primary touchpoint where users make purchase decisions. It needs to be informative, engaging, and guide the user seamlessly through the buying journey. In Flutter, we can construct a highly interactive and modular PDP widget incorporating key features like related items, review carousels, promo badges, and quick buy options.

Understanding the Core Structure

At its heart, a PDP displays essential product information. We'll start with a basic layout and then progressively add more complex components.

1. Essential Product Data Model

First, let's define a simple data model for our product.


class Product {
  final String id;
  final String name;
  final String imageUrl;
  final double price;
  final String description;
  final bool hasPromo;
  final List<Review> reviews;
  final List<String> relatedProductIds;

  Product({
    required this.id,
    required this.name,
    required this.imageUrl,
    required this.price,
    required this.description,
    this.hasPromo = false,
    this.reviews = const [],
    this.relatedProductIds = const [],
  });
}

class Review {
  final String userId;
  final int rating;
  final String comment;
  final DateTime date;

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

2. Basic Product Information Display

The foundation of our PDP will include the product image, name, price, and description. We'll use a Column and SingleChildScrollView for vertical layout.


import 'package:flutter/material.dart';
// Assume Product and Review classes are defined as above

class ProductDetailPage extends StatelessWidget {
  final Product product;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(product.name),
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Image.network(
              product.imageUrl,
              fit: BoxFit.cover,
              width: double.infinity,
              height: 300,
            ),
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    product.name,
                    style: Theme.of(context).textTheme.headlineSmall,
                  ),
                  const SizedBox(height: 8),
                  Text(
                    '\$${product.price.toStringAsFixed(2)}',
                    style: Theme.of(context).textTheme.headlineMedium?.copyWith(
                          color: Colors.deepOrange,
                          fontWeight: FontWeight.bold,
                        ),
                  ),
                  const SizedBox(height: 16),
                  Text(
                    product.description,
                    style: Theme.of(context).textTheme.bodyLarge,
                  ),
                  // More sections will be added here
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Enhancing the PDP with Key Features

3. Integrating a Promo Badge

A promo badge visually highlights special offers. We can overlay it on the product image using a Stack widget.


// Inside ProductDetailPage's build method, replace the Image.network with this Stack
Stack(
  children: [
    Image.network(
      product.imageUrl,
      fit: BoxFit.cover,
      width: double.infinity,
      height: 300,
    ),
    if (product.hasPromo)
      Positioned(
        top: 16,
        right: 16,
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
          decoration: BoxDecoration(
            color: Colors.redAccent,
            borderRadius: BorderRadius.circular(5),
          ),
          child: const Text(
            'PROMO',
            style: TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
              fontSize: 12,
            ),
          ),
        ),
      ),
  ],
),

4. Building a Review Carousel

Customer reviews build trust. A horizontally scrollable carousel (using PageView.builder) is an excellent way to display multiple reviews without consuming too much vertical space.


// Add this section within the Column in the Padding, after product.description
if (product.reviews.isNotEmpty) ...[
  const SizedBox(height: 24),
  Text(
    'Customer Reviews',
    style: Theme.of(context).textTheme.headlineSmall,
  ),
  const SizedBox(height: 16),
  SizedBox(
    height: 150, // Height for the carousel
    child: PageView.builder(
      itemCount: product.reviews.length,
      itemBuilder: (context, index) {
        final review = product.reviews[index];
        return Card(
          margin: const EdgeInsets.symmetric(horizontal: 8),
          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,
                    );
                  }),
                ),
                const SizedBox(height: 8),
                Expanded(
                  child: Text(
                    review.comment,
                    maxLines: 3,
                    overflow: TextOverflow.ellipsis,
                    style: Theme.of(context).textTheme.bodyMedium,
                  ),
                ),
                const SizedBox(height: 4),
                Text(
                  '- ${review.userId} on ${review.date.toLocal().toString().split(' ')[0]}',
                  style: Theme.of(context).textTheme.bodySmall?.copyWith(fontStyle: FontStyle.italic),
                ),
              ],
            ),
          ),
        );
      },
    ),
  ),
],

5. Displaying Related Items

Suggesting related products can increase engagement and average order value. A horizontal ListView.builder is perfect for this.


// Add this section within the Column in the Padding, after the review carousel
if (product.relatedProductIds.isNotEmpty) ...[
  const SizedBox(height: 24),
  Text(
    'You might also like',
    style: Theme.of(context).textTheme.headlineSmall,
  ),
  const SizedBox(height: 16),
  SizedBox(
    height: 200, // Height for related products list
    child: ListView.builder(
      scrollDirection: Axis.horizontal,
      itemCount: product.relatedProductIds.length,
      itemBuilder: (context, index) {
        // In a real app, you would fetch the full Product object
        // using product.relatedProductIds[index] from your data source.
        // For demonstration, let's use a placeholder.
        final relatedProductId = product.relatedProductIds[index];
        return GestureDetector(
          onTap: () {
            // Navigate to the related product's detail page
            // Navigator.push(context, MaterialPageRoute(builder: (context) => ProductDetailPage(product: fetchedRelatedProduct)));
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Tapped on related product $relatedProductId')),
            );
          },
          child: Container(
            width: 150,
            margin: const EdgeInsets.only(right: 12),
            child: Card(
              elevation: 2,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Expanded(
                    child: Image.network(
                      'https://via.placeholder.com/150/FF6347/FFFFFF?text=$relatedProductId', // Placeholder image
                      fit: BoxFit.cover,
                      width: double.infinity,
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: Text(
                      'Related Product $relatedProductId',
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                      style: Theme.of(context).textTheme.titleSmall,
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      },
    ),
  ),
],

6. Implementing Quick Buy Functionality

A quick buy button allows users to add the product to their cart or initiate a purchase directly. This often sits at the bottom of the screen, persistently visible.


// Add this as a bottomNavigationBar outside the SingleChildScrollView, within the Scaffold
bottomNavigationBar: 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: const Offset(0, -5),
      ),
    ],
  ),
  child: Row(
    children: [
      Expanded(
        child: ElevatedButton.icon(
          onPressed: () {
            // Implement add to cart logic
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('${product.name} added to cart!')),
            );
          },
          icon: const Icon(Icons.shopping_cart),
          label: const Text('Add to Cart'),
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.blueAccent,
            foregroundColor: Colors.white,
            padding: const EdgeInsets.symmetric(vertical: 12),
            textStyle: const TextStyle(fontSize: 16),
          ),
        ),
      ),
      const SizedBox(width: 16),
      Expanded(
        child: ElevatedButton(
          onPressed: () {
            // Implement quick buy/checkout logic
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Initiating quick buy for ${product.name}!')),
            );
          },
          child: const Text('Quick Buy'),
          style: ElevatedButton.styleFrom(
            backgroundColor: Colors.deepOrange,
            foregroundColor: Colors.white,
            padding: const EdgeInsets.symmetric(vertical: 12),
            textStyle: const TextStyle(fontSize: 16),
          ),
        ),
      ),
    ],
  ),
),

Putting It All Together (Full Widget Outline)

The complete ProductDetailPage combines all the snippets above into a cohesive and functional widget. Remember to replace placeholder data with actual data fetching logic in a real application.


import 'package:flutter/material.dart';

// Assume Product and Review classes are defined as above

class ProductDetailPage extends StatelessWidget {
  final Product product;

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(product.name),
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // Product Image with Promo Badge
            Stack(
              children: [
                Image.network(
                  product.imageUrl,
                  fit: BoxFit.cover,
                  width: double.infinity,
                  height: 300,
                ),
                if (product.hasPromo)
                  Positioned(
                    top: 16,
                    right: 16,
                    child: Container(
                      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                      decoration: BoxDecoration(
                        color: Colors.redAccent,
                        borderRadius: BorderRadius.circular(5),
                      ),
                      child: const Text(
                        'PROMO',
                        style: TextStyle(
                          color: Colors.white,
                          fontWeight: FontWeight.bold,
                          fontSize: 12,
                        ),
                      ),
                    ),
                  ),
              ],
            ),
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  // Product Name
                  Text(
                    product.name,
                    style: Theme.of(context).textTheme.headlineSmall,
                  ),
                  const SizedBox(height: 8),
                  // Product Price
                  Text(
                    '\$${product.price.toStringAsFixed(2)}',
                    style: Theme.of(context).textTheme.headlineMedium?.copyWith(
                          color: Colors.deepOrange,
                          fontWeight: FontWeight.bold,
                        ),
                  ),
                  const SizedBox(height: 16),
                  // Product Description
                  Text(
                    product.description,
                    style: Theme.of(context).textTheme.bodyLarge,
                  ),
                  // Review Carousel
                  if (product.reviews.isNotEmpty) ...[
                    const SizedBox(height: 24),
                    Text(
                      'Customer Reviews',
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    const SizedBox(height: 16),
                    SizedBox(
                      height: 150, // Height for the carousel
                      child: PageView.builder(
                        itemCount: product.reviews.length,
                        itemBuilder: (context, index) {
                          final review = product.reviews[index];
                          return Card(
                            margin: const EdgeInsets.symmetric(horizontal: 8),
                            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,
                                      );
                                    }),
                                  ),
                                  const SizedBox(height: 8),
                                  Expanded(
                                    child: Text(
                                      review.comment,
                                      maxLines: 3,
                                      overflow: TextOverflow.ellipsis,
                                      style: Theme.of(context).textTheme.bodyMedium,
                                    ),
                                  ),
                                  const SizedBox(height: 4),
                                  Text(
                                    '- ${review.userId} on ${review.date.toLocal().toString().split(' ')[0]}',
                                    style: Theme.of(context).textTheme.bodySmall?.copyWith(fontStyle: FontStyle.italic),
                                  ),
                                ],
                              ),
                            ),
                          );
                        },
                      ),
                    ),
                  ],
                  // Related Items
                  if (product.relatedProductIds.isNotEmpty) ...[
                    const SizedBox(height: 24),
                    Text(
                      'You might also like',
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    const SizedBox(height: 16),
                    SizedBox(
                      height: 200, // Height for related products list
                      child: ListView.builder(
                        scrollDirection: Axis.horizontal,
                        itemCount: product.relatedProductIds.length,
                        itemBuilder: (context, index) {
                          final relatedProductId = product.relatedProductIds[index];
                          return GestureDetector(
                            onTap: () {
                              ScaffoldMessenger.of(context).showSnackBar(
                                SnackBar(content: Text('Tapped on related product $relatedProductId')),
                              );
                            },
                            child: Container(
                              width: 150,
                              margin: const EdgeInsets.only(right: 12),
                              child: Card(
                                elevation: 2,
                                child: Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                    Expanded(
                                      child: Image.network(
                                        'https://via.placeholder.com/150/FF6347/FFFFFF?text=$relatedProductId', // Placeholder image
                                        fit: BoxFit.cover,
                                        width: double.infinity,
                                      ),
                                    ),
                                    Padding(
                                      padding: const EdgeInsets.all(8.0),
                                      child: Text(
                                        'Related Product $relatedProductId',
                                        maxLines: 2,
                                        overflow: TextOverflow.ellipsis,
                                        style: Theme.of(context).textTheme.titleSmall,
                                      ),
                                    ),
                                  ],
                                ),
                              ),
                            ),
                          );
                        },
                      ),
                    ),
                  ],
                  const SizedBox(height: 24), // Add some bottom padding
                ],
              ),
            ),
          ],
        ),
      ),
      // Quick Buy Buttons
      bottomNavigationBar: 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: const Offset(0, -5),
            ),
          ],
        ),
        child: Row(
          children: [
            Expanded(
              child: ElevatedButton.icon(
                onPressed: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('${product.name} added to cart!')),
                  );
                },
                icon: const Icon(Icons.shopping_cart),
                label: const Text('Add to Cart'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.blueAccent,
                  foregroundColor: Colors.white,
                  padding: const EdgeInsets.symmetric(vertical: 12),
                  textStyle: const TextStyle(fontSize: 16),
                ),
              ),
            ),
            const SizedBox(width: 16),
            Expanded(
              child: ElevatedButton(
                onPressed: () {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('Initiating quick buy for ${product.name}!')),
                  );
                },
                child: const Text('Quick Buy'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.deepOrange,
                  foregroundColor: Colors.white,
                  padding: const EdgeInsets.symmetric(vertical: 12),
                  textStyle: const TextStyle(fontSize: 16),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// Example of how to use ProductDetailPage
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final sampleProduct = Product(
      id: '123',
      name: 'Stylish Wireless Headphones',
      imageUrl: 'https://images.unsplash.com/photo-1505740420928-5e589f406525?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
      price: 199.99,
      description: 'Experience premium sound quality with these lightweight and comfortable wireless headphones. Perfect for music lovers on the go, featuring long-lasting battery life and noise cancellation.',
      hasPromo: true,
      reviews: [
        Review(userId: 'User1', rating: 5, comment: 'Amazing sound and comfort!', date: DateTime(2023, 10, 26)),
        Review(userId: 'User2', rating: 4, comment: 'Great value for money, bass could be stronger.', date: DateTime(2023, 10, 20)),
        Review(userId: 'User3', rating: 5, comment: 'Love the design and battery life!', date: DateTime(2023, 10, 15)),
      ],
      relatedProductIds: ['456', '789', '101', '112'],
    );

    return MaterialApp(
      title: 'Product Detail Page Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: ProductDetailPage(product: sampleProduct),
    );
  }
}

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

Conclusion

Building a feature-rich Product Detail Page in Flutter is achievable by breaking it down into modular components. By leveraging widgets like Stack for overlays, PageView.builder for carousels, ListView.builder for horizontal lists, and robust button actions, you can create an engaging and intuitive user experience. Remember to always integrate with a proper data layer (e.g., APIs, local databases) to fetch dynamic product information and ensure a scalable solution.

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