PrivatAI Projects
Need Custom Development?
Get professional help with your Next.js project from our expert team.
Ecommerce Product Components
A collection of reusable ecommerce product components built with Next.js and Tailwind CSS
Project Information
Author
Michael Chen
Technology
Next.js, Tailwind CSS, React
Last Updated
June 10, 2023
Category
UI Components
Components Overview
This collection includes essential components for building modern ecommerce interfaces. Each component is designed to be responsive, accessible, and customizable to fit your brand.
Product Card Component
import { Heart } from 'lucide-react';
import Image from 'next/image';
import Link from 'next/link';
interface ProductCardProps {
id: string;
name: string;
price: number;
image: string;
category: string;
rating: number;
isNew?: boolean;
isSale?: boolean;
discount?: number;
}
export default function ProductCard({
id,
name,
price,
image,
category,
rating,
isNew = false,
isSale = false,
discount = 0,
}: ProductCardProps) {
return (
<div className="group relative bg-white dark:bg-gray-900 rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-shadow duration-300">
{/* Product badges */}
<div className="absolute top-2 left-2 z-10 flex flex-col gap-1">
{isNew && (
<span className="px-2 py-1 text-xs font-medium bg-blue-500 text-white rounded">
New
</span>
)}
{isSale && (
<span className="px-2 py-1 text-xs font-medium bg-red-500 text-white rounded">
Sale
</span>
)}
</div>
{/* Wishlist button */}
<button
className="absolute top-2 right-2 z-10 p-1.5 rounded-full bg-white/80 dark:bg-gray-800/80 hover:bg-white dark:hover:bg-gray-800 transition-colors"
aria-label="Add to wishlist"
>
<Heart className="h-4 w-4 text-gray-600 dark:text-gray-400" />
</button>
{/* Product image */}
<Link href={`/products/${id}`} className="block relative pt-[100%]">
<Image
src={image || "/placeholder.svg"}
alt={name}
fill
className="object-cover object-center group-hover:scale-105 transition-transform duration-300"
/>
</Link>
{/* Product info */}
<div className="p-4">
<span className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider">
{category}
</span>
<Link href={`/products/${id}`}>
<h3 className="mt-1 text-sm font-medium text-gray-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400 transition-colors">
{name}
</h3>
</Link>
{/* Price */}
<div className="mt-2 flex items-center">
{isSale && discount > 0 ? (
<>
<span className="text-sm font-medium text-gray-900 dark:text-white">
{'$'}{(price * (1 - discount / 100)).toFixed(2)}
</span>
<span className="ml-2 text-xs text-gray-500 dark:text-gray-400 line-through">
{'$'}{price.toFixed(2)}
</span>
</>
) : (
<span className="text-sm font-medium text-gray-900 dark:text-white">
{'$'}{price.toFixed(2)}
</span>
)}
</div>
{/* Rating */}
<div className="mt-2 flex items-center">
<div className="flex">
{[...Array(5)].map((_, i) => (
<svg
key={i}
className={`h-3.5 w-3.5 ${
i < rating ? 'text-yellow-400' : 'text-gray-300 dark:text-gray-600'
}`}
fill="currentColor"
viewBox="0 0 20 20"
>
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
</svg>
))}
</div>
<span className="ml-1 text-xs text-gray-500 dark:text-gray-400">
({rating.toFixed(1)})
</span>
</div>
{/* Add to cart button */}
<button className="mt-4 w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-md transition-colors">
Add to Cart
</button>
</div>
</div>
);
}
How to Use
The ProductCard component is designed to display product information in a visually appealing card format. It includes support for badges (new, sale), wishlist functionality, price display with discount calculation, and star ratings.
// Example usage
<ProductCard
id="prod-123"
name="Premium Wireless Headphones"
price={129.99}
image="/images/headphones.jpg"
category="Audio"
rating={4.5}
isNew={true}
isSale={true}
discount={15}
/>
Product Gallery Component
import { useState } from 'react';
import Image from 'next/image';
interface ProductGalleryProps {
images: {
id: string;
src: string;
alt: string;
}[];
}
export default function ProductGallery({ images }: ProductGalleryProps) {
const [activeImage, setActiveImage] = useState(images[0]);
return (
<div className="w-full">
{/* Main image */}
<div className="relative aspect-square w-full overflow-hidden rounded-lg mb-4">
<Image
src={activeImage.src || "/placeholder.svg"}
alt={activeImage.alt}
fill
className="object-cover object-center"
/>
</div>
{/* Thumbnails */}
<div className="flex space-x-2 overflow-x-auto pb-2">
{images.map((image) => (
<button
key={image.id}
onClick={() => setActiveImage(image)}
className={`relative w-20 h-20 rounded-md overflow-hidden ${
activeImage.id === image.id
? 'ring-2 ring-blue-500'
: 'ring-1 ring-gray-300 dark:ring-gray-700'
}`}
>
<Image
src={image.src || "/placeholder.svg"}
alt={`Thumbnail ${image.alt}`}
fill
className="object-cover object-center"
/>
</button>
))}
</div>
</div>
);
}
How to Use
The ProductGallery component displays a main product image with clickable thumbnails below. Users can click on thumbnails to change the main image view.
// Example usage
const productImages = [
{
id: "img1",
src: "/images/product-1.jpg",
alt: "Product front view"
},
{
id: "img2",
src: "/images/product-2.jpg",
alt: "Product side view"
},
{
id: "img3",
src: "/images/product-3.jpg",
alt: "Product back view"
}
];
<ProductGallery images={productImages} />
Product Variants Component
import { useState } from 'react';
interface ColorOption {
id: string;
name: string;
value: string;
}
interface SizeOption {
id: string;
name: string;
inStock: boolean;
}
interface ProductVariantsProps {
colors: ColorOption[];
sizes: SizeOption[];
onVariantChange: (color: string, size: string) => void;
}
export default function ProductVariants({
colors,
sizes,
onVariantChange,
}: ProductVariantsProps) {
const [selectedColor, setSelectedColor] = useState<string>(colors[0]?.id || '');
const [selectedSize, setSelectedSize] = useState<string>('');
const handleColorChange = (colorId: string) => {
setSelectedColor(colorId);
onVariantChange(colorId, selectedSize);
};
const handleSizeChange = (sizeId: string) => {
setSelectedSize(sizeId);
onVariantChange(selectedColor, sizeId);
};
return (
<div className="space-y-6">
{/* Color options */}
<div>
<h3 className="text-sm font-medium text-gray-900 dark:text-white mb-3">Color</h3>
<div className="flex flex-wrap gap-2">
{colors.map((color) => (
<button
key={color.id}
onClick={() => handleColorChange(color.id)}
className={`relative w-9 h-9 rounded-full ${
selectedColor === color.id
? 'ring-2 ring-offset-2 ring-gray-900 dark:ring-gray-100'
: ''
}`}
style={{ backgroundColor: color.value }}
aria-label={color.name}
>
<span className="sr-only">{color.name}</span>
</button>
))}
</div>
</div>
{/* Size options */}
<div>
<div className="flex items-center justify-between">
<h3 className="text-sm font-medium text-gray-900 dark:text-white">Size</h3>
<a href="#size-guide" className="text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-500">
Size guide
</a>
</div>
<div className="grid grid-cols-4 gap-2 mt-3">
{sizes.map((size) => (
<button
key={size.id}
onClick={() => size.inStock && handleSizeChange(size.id)}
disabled={!size.inStock}
className={`py-2 px-3 text-sm font-medium rounded-md border ${
selectedSize === size.id
? 'bg-gray-900 dark:bg-gray-700 text-white border-gray-900 dark:border-gray-700'
: size.inStock
? 'bg-white dark:bg-gray-800 text-gray-900 dark:text-white border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700'
: 'bg-gray-100 dark:bg-gray-900 text-gray-400 dark:text-gray-500 border-gray-200 dark:border-gray-800 cursor-not-allowed'
}`}
>
{size.name}
</button>
))}
</div>
</div>
</div>
);
}
How to Use
The ProductVariants component allows users to select color and size options for a product. It handles the state of selected variants and provides callbacks when selections change.
// Example usage
const colorOptions = [
{ id: "color1", name: "Black", value: "#000000" },
{ id: "color2", name: "White", value: "#ffffff" },
{ id: "color3", name: "Red", value: "#ef4444" }
];
const sizeOptions = [
{ id: "size1", name: "XS", inStock: true },
{ id: "size2", name: "S", inStock: true },
{ id: "size3", name: "M", inStock: true },
{ id: "size4", name: "L", inStock: false },
{ id: "size5", name: "XL", inStock: true }
];
const handleVariantChange = (colorId, sizeId) => {
console.log('Selected color:', colorId);
console.log('Selected size:', sizeId);
// Update product price, availability, etc.
};
<ProductVariants
colors={colorOptions}
sizes={sizeOptions}
onVariantChange={handleVariantChange}
/>
Implementation Guide
1. Set Up Your Project
Start with a Next.js project using Tailwind CSS. If you don't have one, create it using:
npx create-next-app@latest my-ecommerce --typescript --tailwind --eslint
2. Create Component Directory
Organize your components in a structured way:
mkdir -p components/products
3. Add Components
Copy the component code into separate files in your components/products directory:
- components/products/ProductCard.tsx
- components/products/ProductGallery.tsx
- components/products/ProductVariants.tsx
4. Create a Product Page
Combine the components to create a complete product page:
// app/products/[id]/page.tsx
import ProductGallery from '@/components/products/ProductGallery';
import ProductVariants from '@/components/products/ProductVariants';
import { ShoppingCart } from 'lucide-react';
// This would typically come from an API or database
const getProductData = (id: string) => ({
id,
name: 'Premium Wireless Headphones',
description: 'Experience crystal-clear audio with our premium wireless headphones. Features noise cancellation and 20-hour battery life.',
price: 129.99,
images: [
{ id: 'img1', src: '/placeholder.svg?height=600&width=600', alt: 'Headphones front view' },
{ id: 'img2', src: '/placeholder.svg?height=600&width=600', alt: 'Headphones side view' },
{ id: 'img3', src: '/placeholder.svg?height=600&width=600', alt: 'Headphones back view' },
],
colors: [
{ id: 'color1', name: 'Black', value: '#000000' },
{ id: 'color2', name: 'White', value: '#ffffff' },
{ id: 'color3', name: 'Blue', value: '#3b82f6' },
],
sizes: [
{ id: 'size1', name: 'One Size', inStock: true },
],
});
export default function ProductPage({ params }: { params: { id: string } }) {
const product = getProductData(params.id);
return (
<div className="max-w-7xl mx-auto px-4 py-10">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Product Gallery */}
<ProductGallery images={product.images} />
{/* Product Info */}
<div className="space-y-6">
<h1 className="text-2xl font-bold">{product.name}</h1>
<p className="text-xl font-medium">{'$'}{product.price.toFixed(2)}</p>
<div className="border-t border-gray-200 dark:border-gray-800 pt-6">
<p className="text-gray-600 dark:text-gray-300">{product.description}</p>
</div>
{/* Product Variants */}
<ProductVariants
colors={product.colors}
sizes={product.sizes}
onVariantChange={(color, size) => {
console.log('Selected:', color, size);
}}
/>
{/* Add to Cart */}
<button className="w-full py-3 px-4 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-md flex items-center justify-center">
<ShoppingCart className="mr-2 h-5 w-5" />
Add to Cart
</button>
</div>
</div>
</div>
);
}
Best Practices
Accessibility
Ensure all interactive elements have proper ARIA attributes and keyboard navigation support.
Performance
Use Next.js Image component with proper sizing and formats to optimize image loading.
Responsive Design
Test components on various screen sizes and ensure they adapt appropriately.
State Management
For larger applications, consider using React Context or a state management library like Zustand for cart functionality.
Ready to Use These Components?
Download the complete component collection and start building your ecommerce site today!