E-commerce

Grocery Home Screen

A pixel-faithful Flutter home screen for an online grocery marketplace. Includes a brand header with location row, a rounded search bar, a swipeable banner carousel with animated dot indicators, horizontally scrollable Exclusive Offer and Best Selling product cards with green add-to-cart buttons, a Groceries category strip and a five-tab rounded bottom navigation. Pure Flutter SDK widgets — no external packages, easy to theme.

groceryhomemarketplace

WHAT'S INCLUDED

  • Brand header with leaf logo, location pin and city label
  • Rounded muted search bar with leading icon and hint
  • PageView banner carousel with animated pill dot indicator
  • Horizontal Exclusive Offer and Best Selling product card lists
  • Groceries category strip with colored tile backgrounds
  • Five-tab rounded sticky bottom nav: Shop, Explore, Cart, Favourite, Account

USE CASES

  • Home screen for any grocery, food or local-delivery app
  • Marketplace landing page for fresh produce or daily essentials
  • Reusable storefront feed for D2C and FMCG products
  • Quick-commerce starter UI for a 10-minute delivery app

PRO TIP

Wrap horizontal product lists in a fixed-height SizedBox and use ListView.builder rather than mapping a Row of cards. ListView keeps off-screen widgets disposed and gives you free physics plus cached extents, so your home feed stays buttery even with 100+ products. Combine with a CustomScrollView and Slivers if you want the search bar to pin while the banner scrolls away.

Complete Dart Code

Copy this into your Flutter project. No external packages required.

dart
import 'package:flutter/material.dart';

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

  @override
  State<GroceryHomeScreen> createState() => _GroceryHomeScreenState();
}

class _GroceryHomeScreenState extends State<GroceryHomeScreen> {
  static const Color _primary = Color(0xFF53B175);
  static const Color _surface = Color(0xFFFFFFFF);
  static const Color _surfaceMuted = Color(0xFFF2F3F2);
  static const Color _textPrimary = Color(0xFF181725);
  static const Color _textMuted = Color(0xFF7C7C7C);
  static const Color _textSubtle = Color(0xFF4C4F4D);
  static const Color _border = Color(0xFFE2E2E2);

  late final PageController _bannerController;
  late final TextEditingController _searchController;
  int _navIndex = 0;
  int _bannerIndex = 0;

  static const List<_Banner> _banners = <_Banner>[
    _Banner(title: 'Fresh Vegetables', subtitle: 'Get Up To 40% OFF'),
    _Banner(title: 'Fresh Fruits', subtitle: 'Sweet Summer Deals'),
    _Banner(title: 'Daily Essentials', subtitle: 'Up To 25% OFF'),
  ];

  static const List<_Product> _exclusive = <_Product>[
    _Product(emoji: '🍌', name: 'Organic Bananas', subtitle: '7pcs, Price', price: '\$4.99'),
    _Product(emoji: '🍎', name: 'Red Apple', subtitle: '1kg, Price', price: '\$4.99'),
    _Product(emoji: '🥭', name: 'Mango', subtitle: '500g, Price', price: '\$3.49'),
  ];

  static const List<_Product> _bestSelling = <_Product>[
    _Product(emoji: '🍓', name: 'Strawberry', subtitle: '500g, Price', price: '\$5.49'),
    _Product(emoji: '🫚', name: 'Ginger', subtitle: '250g, Price', price: '\$2.99'),
    _Product(emoji: '🍍', name: 'Pineapple', subtitle: '1pc, Price', price: '\$3.99'),
  ];

  static const List<_Category> _categories = <_Category>[
    _Category(name: 'Pulses', emoji: '🌾', bg: Color(0xFFFDE598)),
    _Category(name: 'Rice', emoji: '🌾', bg: Color(0xFFD3B0E0)),
    _Category(name: 'Meat', emoji: '🥩', bg: Color(0xFFFCB7AF)),
  ];

  static const List<_NavItem> _navItems = <_NavItem>[
    _NavItem(label: 'Shop', icon: Icons.storefront_outlined),
    _NavItem(label: 'Explore', icon: Icons.search),
    _NavItem(label: 'Cart', icon: Icons.shopping_cart_outlined),
    _NavItem(label: 'Favourite', icon: Icons.favorite_border),
    _NavItem(label: 'Account', icon: Icons.person_outline),
  ];

  @override
  void initState() {
    super.initState();
    _bannerController = PageController();
    _searchController = TextEditingController();
  }

  @override
  void dispose() {
    _bannerController.dispose();
    _searchController.dispose();
    super.dispose();
  }

  Future<void> _onAddToCart(_Product product) async {
    // TODO: Replace with your cart / Firestore / Riverpod / Bloc state update.
  }

  void _onSeeAll(String section) {
    // TODO: Navigate to /${section} listing screen.
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: _surface,
      bottomNavigationBar: _buildBottomNav(),
      body: SafeArea(
        bottom: false,
        child: SingleChildScrollView(
          padding: const EdgeInsets.fromLTRB(20, 12, 20, 24),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _buildBrandHeader(),
              const SizedBox(height: 22),
              _buildSearch(),
              const SizedBox(height: 18),
              _buildBanner(),
              const SizedBox(height: 24),
              _buildSectionHeader('Exclusive Offer', () => _onSeeAll('exclusive')),
              const SizedBox(height: 14),
              _buildHorizontalProducts(_exclusive),
              const SizedBox(height: 24),
              _buildSectionHeader('Best Selling', () => _onSeeAll('best-selling')),
              const SizedBox(height: 14),
              _buildHorizontalProducts(_bestSelling),
              const SizedBox(height: 24),
              _buildSectionHeader('Groceries', () => _onSeeAll('groceries')),
              const SizedBox(height: 14),
              _buildCategories(),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildBrandHeader() {
    return Column(
      children: const [
        Icon(Icons.spa, size: 28, color: _primary),
        SizedBox(height: 4),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.location_on_outlined, size: 16, color: _textSubtle),
            SizedBox(width: 4),
            Text(
              'Dhaka, Banassre',
              style: TextStyle(
                fontSize: 15,
                fontWeight: FontWeight.w600,
                color: _textSubtle,
                letterSpacing: -0.2,
              ),
            ),
          ],
        ),
      ],
    );
  }

  Widget _buildSearch() {
    return Container(
      decoration: BoxDecoration(
        color: _surfaceMuted,
        borderRadius: BorderRadius.circular(15),
      ),
      padding: const EdgeInsets.symmetric(horizontal: 14),
      child: Row(
        children: [
          const Icon(Icons.search, size: 22, color: _textPrimary),
          const SizedBox(width: 10),
          Expanded(
            child: TextField(
              controller: _searchController,
              decoration: const InputDecoration(
                hintText: 'Search Store',
                hintStyle: TextStyle(
                  color: _textMuted,
                  fontSize: 15,
                  fontWeight: FontWeight.w500,
                ),
                border: InputBorder.none,
                contentPadding: EdgeInsets.symmetric(vertical: 14),
                isDense: true,
              ),
              style: const TextStyle(fontSize: 15, color: _textPrimary),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildBanner() {
    return AspectRatio(
      aspectRatio: 2,
      child: Stack(
        children: [
          PageView.builder(
            controller: _bannerController,
            itemCount: _banners.length,
            onPageChanged: (i) => setState(() => _bannerIndex = i),
            itemBuilder: (_, i) => _BannerSlide(banner: _banners[i]),
          ),
          Positioned(
            left: 0,
            right: 0,
            bottom: 10,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: List<Widget>.generate(_banners.length, (i) {
                final bool active = i == _bannerIndex;
                return AnimatedContainer(
                  duration: const Duration(milliseconds: 200),
                  width: active ? 18 : 6,
                  height: 6,
                  margin: const EdgeInsets.symmetric(horizontal: 2),
                  decoration: BoxDecoration(
                    color: active ? _primary : Colors.white.withOpacity(0.7),
                    borderRadius: BorderRadius.circular(3),
                  ),
                );
              }),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildSectionHeader(String title, VoidCallback onSeeAll) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(
          title,
          style: const TextStyle(
            fontSize: 18,
            fontWeight: FontWeight.w700,
            color: _textPrimary,
            letterSpacing: -0.4,
          ),
        ),
        GestureDetector(
          onTap: onSeeAll,
          child: const Text(
            'See all',
            style: TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w600,
              color: _primary,
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildHorizontalProducts(List<_Product> products) {
    return SizedBox(
      height: 220,
      child: ListView.separated(
        scrollDirection: Axis.horizontal,
        itemCount: products.length,
        separatorBuilder: (_, __) => const SizedBox(width: 14),
        itemBuilder: (_, i) => _buildProductCard(products[i]),
        physics: const BouncingScrollPhysics(),
      ),
    );
  }

  Widget _buildProductCard(_Product product) {
    return Container(
      width: 160,
      decoration: BoxDecoration(
        color: _surface,
        border: Border.all(color: _border),
        borderRadius: BorderRadius.circular(18),
      ),
      padding: const EdgeInsets.all(14),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Center(child: Text(product.emoji, style: const TextStyle(fontSize: 56))),
          const SizedBox(height: 8),
          Text(
            product.name,
            style: const TextStyle(
              fontSize: 15,
              fontWeight: FontWeight.w700,
              color: _textPrimary,
            ),
          ),
          const SizedBox(height: 4),
          Text(
            product.subtitle,
            style: const TextStyle(fontSize: 12, color: _textMuted),
          ),
          const Spacer(),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                product.price,
                style: const TextStyle(
                  fontSize: 17,
                  fontWeight: FontWeight.w700,
                  color: _textPrimary,
                ),
              ),
              GestureDetector(
                onTap: () => _onAddToCart(product),
                child: Container(
                  width: 44,
                  height: 44,
                  decoration: BoxDecoration(
                    color: _primary,
                    borderRadius: BorderRadius.circular(15),
                    boxShadow: const [
                      BoxShadow(
                        color: Color(0x4D53B175),
                        blurRadius: 12,
                        offset: Offset(0, 4),
                      ),
                    ],
                  ),
                  child: const Icon(Icons.add, color: Colors.white, size: 20),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildCategories() {
    return SizedBox(
      height: 110,
      child: ListView.separated(
        scrollDirection: Axis.horizontal,
        itemCount: _categories.length,
        separatorBuilder: (_, __) => const SizedBox(width: 14),
        itemBuilder: (_, i) {
          final c = _categories[i];
          return Container(
            width: 250,
            decoration: BoxDecoration(
              color: c.bg,
              borderRadius: BorderRadius.circular(18),
            ),
            padding: const EdgeInsets.symmetric(horizontal: 14),
            child: Row(
              children: [
                Text(c.emoji, style: const TextStyle(fontSize: 56)),
                const SizedBox(width: 14),
                Text(
                  c.name,
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.w700,
                    color: _textPrimary,
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }

  Widget _buildBottomNav() {
    return Container(
      decoration: const BoxDecoration(
        color: _surface,
        borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
        boxShadow: [
          BoxShadow(color: Color(0x14000000), blurRadius: 18, offset: Offset(0, -4)),
        ],
      ),
      padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
      child: SafeArea(
        top: false,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: List<Widget>.generate(_navItems.length, (i) {
            final bool selected = i == _navIndex;
            final item = _navItems[i];
            return GestureDetector(
              behavior: HitTestBehavior.opaque,
              onTap: () => setState(() => _navIndex = i),
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 6),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Icon(item.icon, size: 24, color: selected ? _primary : _textMuted),
                    const SizedBox(height: 4),
                    Text(
                      item.label,
                      style: TextStyle(
                        fontSize: 11,
                        fontWeight: FontWeight.w500,
                        color: selected ? _textPrimary : _textMuted,
                      ),
                    ),
                  ],
                ),
              ),
            );
          }),
        ),
      ),
    );
  }
}

class _BannerSlide extends StatelessWidget {
  const _BannerSlide({required this.banner});
  final _Banner banner;

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [Color(0xFFF4D2D2), Color(0xFFFCE9C2), Color(0xFFCDE9CE)],
        ),
        borderRadius: BorderRadius.circular(18),
      ),
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18),
      child: Row(
        children: [
          Expanded(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  banner.title,
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.w700,
                    color: Color(0xFF1F4E1A),
                    letterSpacing: -0.3,
                  ),
                ),
                const SizedBox(height: 4),
                Text(
                  banner.subtitle,
                  style: const TextStyle(
                    fontSize: 13,
                    fontWeight: FontWeight.w600,
                    color: Color(0xFF53B175),
                  ),
                ),
              ],
            ),
          ),
          const Text('🥕🥦', style: TextStyle(fontSize: 38)),
        ],
      ),
    );
  }
}

class _Banner {
  final String title;
  final String subtitle;
  const _Banner({required this.title, required this.subtitle});
}

class _Product {
  final String emoji;
  final String name;
  final String subtitle;
  final String price;
  const _Product({
    required this.emoji,
    required this.name,
    required this.subtitle,
    required this.price,
  });
}

class _Category {
  final String name;
  final String emoji;
  final Color bg;
  const _Category({required this.name, required this.emoji, required this.bg});
}

class _NavItem {
  final String label;
  final IconData icon;
  const _NavItem({required this.label, required this.icon});
}

How to use this widget

  1. 1Copy the Dart code above
  2. 2Create a new file — grocery_home_screen.dart
  3. 3Paste the code and route to GroceryHomeScreen(). Replace the TODO comments with your cart, navigation and search wiring.

Related components

E-commerce

Course Detail Page

Course detail with hero image, mentor, tabs, lessons and sticky enroll bar

View component →
E-commerce

Course Detail Screen

E-learning home with promo banner, mentors, category chips and course cards

View component →
Auth & Onboarding

Login Screen

Clean email + password login with form validation and loading state

View component →