Course Detail Page
Course detail with hero image, mentor, tabs, lessons and sticky enroll bar
View component →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.
WHAT'S INCLUDED
USE CASES
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.
Copy this into your Flutter project. No external packages required.
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});
}
Course detail with hero image, mentor, tabs, lessons and sticky enroll bar
View component →E-learning home with promo banner, mentors, category chips and course cards
View component →Clean email + password login with form validation and loading state
View component →