Pages Hard
Product Detail Page
A full e-commerce product page with image gallery, variant picker, quantity selector, add-to-cart, and a reviews section. No libraries.
Open in Lab
MCP
vanilla-js css
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #fff;
color: #111;
}
.site-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 40px;
border-bottom: 1px solid #e5e7eb;
position: sticky;
top: 0;
background: #fff;
z-index: 10;
}
.logo {
font-size: 20px;
font-weight: 800;
text-decoration: none;
color: #111;
}
.header-nav {
display: flex;
align-items: center;
gap: 24px;
}
.header-nav a {
font-size: 14px;
color: #555;
text-decoration: none;
}
.cart-btn {
position: relative;
background: none;
border: none;
cursor: pointer;
padding: 4px;
}
.cart-btn svg {
width: 22px;
height: 22px;
color: #111;
}
.cart-count {
position: absolute;
top: -4px;
right: -4px;
width: 16px;
height: 16px;
background: #6366f1;
color: #fff;
font-size: 10px;
font-weight: 700;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 40px;
font-size: 13px;
color: #888;
border-bottom: 1px solid #f3f4f6;
}
.breadcrumb a {
color: #6366f1;
text-decoration: none;
}
/* Product layout */
.product-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 60px;
max-width: 1100px;
margin: 0 auto;
padding: 40px;
}
@media (max-width: 768px) {
.product-layout {
grid-template-columns: 1fr;
gap: 32px;
padding: 24px;
}
.site-header {
padding: 16px 24px;
}
.breadcrumb {
padding: 12px 24px;
}
}
/* Gallery */
.gallery-main {
width: 100%;
border-radius: 16px;
overflow: hidden;
background: #f3f4f6;
aspect-ratio: 1;
}
.gallery-main img {
width: 100%;
height: 100%;
object-fit: cover;
transition: opacity 0.25s;
}
.gallery-thumbs {
display: flex;
gap: 10px;
margin-top: 12px;
}
.thumb {
width: 72px;
height: 72px;
border-radius: 10px;
overflow: hidden;
border: 2px solid transparent;
cursor: pointer;
padding: 0;
background: #f3f4f6;
transition: border-color 0.15s;
flex-shrink: 0;
}
.thumb.active {
border-color: #6366f1;
}
.thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Product info */
.product-tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.tag {
display: inline-block;
padding: 3px 10px;
background: #eef2ff;
color: #6366f1;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.tag.green {
background: #f0fdf4;
color: #16a34a;
}
h1 {
font-size: clamp(22px, 3vw, 28px);
font-weight: 800;
margin: 12px 0 8px;
}
.product-rating {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #555;
margin-bottom: 12px;
}
.stars {
color: #f59e0b;
font-size: 16px;
}
.price-row {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.price {
font-size: 28px;
font-weight: 800;
color: #111;
}
.price-original {
font-size: 18px;
color: #aaa;
text-decoration: line-through;
}
.discount-badge {
background: #fef2f2;
color: #ef4444;
padding: 3px 8px;
border-radius: 6px;
font-size: 12px;
font-weight: 700;
}
.product-desc {
font-size: 15px;
color: #555;
line-height: 1.7;
margin-bottom: 24px;
}
/* Options */
.option-group {
margin-bottom: 20px;
}
.option-group label {
font-size: 14px;
font-weight: 600;
color: #111;
display: block;
margin-bottom: 10px;
}
.option-group label strong {
font-weight: 700;
}
.color-swatches {
display: flex;
gap: 10px;
}
.swatch {
width: 32px;
height: 32px;
border-radius: 50%;
border: 3px solid transparent;
cursor: pointer;
transition: transform 0.15s;
outline: none;
}
.swatch.active {
border-color: #6366f1;
transform: scale(1.1);
box-shadow: 0 0 0 2px #fff, 0 0 0 4px #6366f1;
}
.size-btns {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.size-btn {
padding: 8px 18px;
border: 1.5px solid #e5e7eb;
border-radius: 8px;
background: #fff;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
}
.size-btn.active {
border-color: #6366f1;
color: #6366f1;
background: #eef2ff;
}
.size-btn.sold-out {
color: #aaa;
text-decoration: line-through;
cursor: not-allowed;
}
.quantity-row {
display: flex;
align-items: center;
gap: 16px;
}
.qty-stepper {
display: flex;
align-items: center;
border: 1.5px solid #e5e7eb;
border-radius: 10px;
overflow: hidden;
}
.qty-btn {
width: 38px;
height: 38px;
border: none;
background: #f9fafb;
font-size: 18px;
cursor: pointer;
transition: background 0.15s;
}
.qty-btn:hover {
background: #f3f4f6;
}
.qty-val {
width: 44px;
text-align: center;
font-size: 15px;
font-weight: 600;
}
.stock-note {
font-size: 12px;
color: #16a34a;
font-weight: 500;
}
/* Actions */
.action-buttons {
display: flex;
gap: 12px;
margin-bottom: 20px;
}
.btn-cart {
flex: 1;
padding: 14px;
background: #111;
color: #fff;
border: none;
border-radius: 12px;
font-size: 15px;
font-weight: 700;
cursor: pointer;
transition: opacity 0.15s;
}
.btn-cart:active {
opacity: 0.85;
}
.btn-cart.loading {
opacity: 0.7;
cursor: wait;
}
.btn-cart.success {
background: #16a34a;
}
.btn-wishlist {
width: 52px;
height: 52px;
border: 1.5px solid #e5e7eb;
border-radius: 12px;
background: #fff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: border-color 0.15s;
}
.btn-wishlist:hover {
border-color: #f43f5e;
}
.btn-wishlist svg {
width: 20px;
height: 20px;
color: #555;
}
.btn-wishlist.active svg {
fill: #f43f5e;
stroke: #f43f5e;
}
.product-meta {
display: flex;
gap: 16px;
flex-wrap: wrap;
font-size: 12px;
color: #888;
}
/* Reviews */
.reviews-section {
max-width: 1100px;
margin: 0 auto;
padding: 40px;
border-top: 1px solid #f3f4f6;
}
.reviews-section h2 {
font-size: 22px;
font-weight: 700;
margin-bottom: 32px;
}
.reviews-layout {
display: grid;
grid-template-columns: 280px 1fr;
gap: 40px;
}
@media (max-width: 768px) {
.reviews-layout {
grid-template-columns: 1fr;
}
}
.big-rating {
font-size: 64px;
font-weight: 900;
color: #111;
line-height: 1;
}
.stars.big {
font-size: 24px;
margin-top: 4px;
}
.rating-count {
font-size: 14px;
color: #888;
margin: 8px 0 16px;
}
.rating-bars {
display: flex;
flex-direction: column;
gap: 6px;
}
.rating-bar {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: #888;
}
.bar {
flex: 1;
height: 6px;
background: #f3f4f6;
border-radius: 3px;
overflow: hidden;
}
.fill {
height: 100%;
background: #f59e0b;
border-radius: 3px;
}
.review-list {
display: flex;
flex-direction: column;
gap: 20px;
}
.review-card {
padding: 20px;
border: 1px solid #f3f4f6;
border-radius: 12px;
}
.review-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.reviewer-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.02em;
}
.av1 {
background: linear-gradient(135deg, #6366f1, #8b5cf6);
}
.av2 {
background: linear-gradient(135deg, #f59e0b, #ef4444);
}
.av3 {
background: linear-gradient(135deg, #10b981, #3b82f6);
}
.review-header div {
flex: 1;
}
.review-header strong {
font-size: 14px;
color: #111;
display: block;
}
.review-header span {
font-size: 12px;
color: #888;
}
.review-card p {
font-size: 14px;
color: #555;
line-height: 1.6;
}// Gallery
const mainImage = document.getElementById("mainImage");
document.querySelectorAll(".thumb").forEach((thumb) => {
thumb.addEventListener("click", () => {
document.querySelectorAll(".thumb").forEach((t) => t.classList.remove("active"));
thumb.classList.add("active");
mainImage.style.opacity = "0";
setTimeout(() => {
mainImage.src = thumb.dataset.src;
mainImage.style.opacity = "1";
}, 150);
});
});
// Color picker
document.querySelectorAll(".swatch").forEach((swatch) => {
swatch.addEventListener("click", () => {
document.querySelectorAll(".swatch").forEach((s) => s.classList.remove("active"));
swatch.classList.add("active");
document.getElementById("selectedColor").textContent = swatch.dataset.color;
});
});
// Size picker
document.querySelectorAll(".size-btn:not(.sold-out)").forEach((btn) => {
btn.addEventListener("click", () => {
document.querySelectorAll(".size-btn").forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
});
});
// Quantity stepper
let qty = 1;
const qtyVal = document.getElementById("qtyVal");
document.getElementById("qtyDec").addEventListener("click", () => {
if (qty > 1) {
qty--;
qtyVal.textContent = qty;
}
});
document.getElementById("qtyInc").addEventListener("click", () => {
if (qty < 12) {
qty++;
qtyVal.textContent = qty;
}
});
// Add to cart
const cartBtn = document.getElementById("addToCart");
cartBtn.addEventListener("click", () => {
if (cartBtn.classList.contains("loading")) return;
cartBtn.classList.add("loading");
cartBtn.textContent = "Adding…";
setTimeout(() => {
cartBtn.classList.remove("loading");
cartBtn.classList.add("success");
cartBtn.textContent = "✓ Added to Cart";
setTimeout(() => {
cartBtn.classList.remove("success");
cartBtn.textContent = "Add to Cart";
}, 2000);
}, 900);
});
// Wishlist toggle
document.querySelector(".btn-wishlist").addEventListener("click", function () {
this.classList.toggle("active");
});<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<title>Product Detail</title>
</head>
<body>
<header class="site-header">
<a href="#" class="logo">Brand</a>
<nav class="header-nav">
<a href="#">Products</a>
<a href="#">About</a>
<button class="cart-btn" aria-label="Cart">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 0 1-8 0"/></svg>
<span class="cart-count">2</span>
</button>
</nav>
</header>
<div class="breadcrumb">
<a href="#">Home</a>
<span>/</span>
<a href="#">Bags</a>
<span>/</span>
<span>Minimal Backpack</span>
</div>
<main class="product-layout">
<!-- Gallery -->
<div class="gallery">
<div class="gallery-main">
<img id="mainImage" src="https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=600&q=80" alt="Minimal Backpack" />
</div>
<div class="gallery-thumbs">
<button class="thumb active" data-src="https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=600&q=80">
<img src="https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=120&q=70" alt="View 1" />
</button>
<button class="thumb" data-src="https://images.unsplash.com/photo-1491637639811-60e2756cc1c7?w=600&q=80">
<img src="https://images.unsplash.com/photo-1491637639811-60e2756cc1c7?w=120&q=70" alt="View 2" />
</button>
<button class="thumb" data-src="https://images.unsplash.com/photo-1548036328-c9fa89d128fa?w=600&q=80">
<img src="https://images.unsplash.com/photo-1548036328-c9fa89d128fa?w=120&q=70" alt="View 3" />
</button>
<button class="thumb" data-src="https://images.unsplash.com/photo-1566150905458-1bf1fc113f0d?w=600&q=80">
<img src="https://images.unsplash.com/photo-1566150905458-1bf1fc113f0d?w=120&q=70" alt="View 4" />
</button>
</div>
</div>
<!-- Info -->
<div class="product-info">
<div class="product-tags">
<span class="tag">New</span>
<span class="tag green">In Stock</span>
</div>
<h1>Minimal Backpack</h1>
<div class="product-rating">
<div class="stars">★★★★★</div>
<span>4.8 (124 reviews)</span>
</div>
<div class="price-row">
<span class="price">$129.00</span>
<span class="price-original">$159.00</span>
<span class="discount-badge">–19%</span>
</div>
<p class="product-desc">A clean, minimal 20L backpack built for everyday carry. Water-resistant canvas, padded laptop sleeve, and ergonomic straps designed to carry you through long days.</p>
<!-- Color picker -->
<div class="option-group">
<label>Color: <strong id="selectedColor">Sand</strong></label>
<div class="color-swatches">
<button class="swatch active" data-color="Sand" style="background: #c8b89a;" aria-label="Sand"></button>
<button class="swatch" data-color="Black" style="background: #1a1a1a;" aria-label="Black"></button>
<button class="swatch" data-color="Slate" style="background: #64748b;" aria-label="Slate"></button>
<button class="swatch" data-color="Olive" style="background: #6b7a3a;" aria-label="Olive"></button>
</div>
</div>
<!-- Size picker -->
<div class="option-group">
<label>Size</label>
<div class="size-btns">
<button class="size-btn">18L</button>
<button class="size-btn active">20L</button>
<button class="size-btn">26L</button>
<button class="size-btn sold-out" disabled>32L</button>
</div>
</div>
<!-- Quantity -->
<div class="option-group">
<label>Quantity</label>
<div class="quantity-row">
<div class="qty-stepper">
<button class="qty-btn" id="qtyDec">−</button>
<span class="qty-val" id="qtyVal">1</span>
<button class="qty-btn" id="qtyInc">+</button>
</div>
<span class="stock-note">12 left in stock</span>
</div>
</div>
<!-- Actions -->
<div class="action-buttons">
<button class="btn-cart" id="addToCart">Add to Cart</button>
<button class="btn-wishlist" aria-label="Add to wishlist">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>
</button>
</div>
<div class="product-meta">
<span>SKU: BAG-MIN-20L</span>
<span>Free shipping over $100</span>
<span>30-day returns</span>
</div>
</div>
</main>
<!-- Reviews -->
<section class="reviews-section">
<h2>Customer Reviews</h2>
<div class="reviews-layout">
<div class="rating-summary">
<div class="big-rating">4.8</div>
<div class="stars big">★★★★★</div>
<div class="rating-count">124 reviews</div>
<div class="rating-bars">
<div class="rating-bar"><span>5★</span><div class="bar"><div class="fill" style="width:78%"></div></div><span>78%</span></div>
<div class="rating-bar"><span>4★</span><div class="bar"><div class="fill" style="width:14%"></div></div><span>14%</span></div>
<div class="rating-bar"><span>3★</span><div class="bar"><div class="fill" style="width:5%"></div></div><span>5%</span></div>
<div class="rating-bar"><span>2★</span><div class="bar"><div class="fill" style="width:2%"></div></div><span>2%</span></div>
<div class="rating-bar"><span>1★</span><div class="bar"><div class="fill" style="width:1%"></div></div><span>1%</span></div>
</div>
</div>
<div class="review-list">
<div class="review-card">
<div class="review-header"><div class="reviewer-avatar av1">AM</div><div><strong>Alex M.</strong><span>Verified buyer · March 2026</span></div><div class="stars">★★★★★</div></div>
<p>Best backpack I've owned. The laptop sleeve fits my 16" MacBook perfectly and the canvas is genuinely water resistant. Worth every penny.</p>
</div>
<div class="review-card">
<div class="review-header"><div class="reviewer-avatar av2">SR</div><div><strong>Sam R.</strong><span>Verified buyer · Feb 2026</span></div><div class="stars">★★★★★</div></div>
<p>Minimal and beautiful. Gets compliments every day. The straps are surprisingly comfortable for a pack this clean-looking.</p>
</div>
<div class="review-card">
<div class="review-header"><div class="reviewer-avatar av3">JL</div><div><strong>Jordan L.</strong><span>Verified buyer · Jan 2026</span></div><div class="stars">★★★★☆</div></div>
<p>Love the design. Wish the 32L was in stock. The 20L is a bit small for travel but perfect for daily commuting.</p>
</div>
</div>
</div>
</section>
<script src="script.js"></script>
</body>
</html>Product Detail Page
A production-quality e-commerce product page covering the core PDP layout: thumbnail gallery with zoom preview, color/size variant selectors, quantity stepper, add-to-cart with loading state, and a star-rated review section.
Features
- Image gallery — thumbnail strip + main image with fade transition
- Variant picker — color swatches and size buttons with active/sold-out states
- Quantity stepper — min/max clamped increment/decrement
- Add to cart — loading spinner → success animation
- Reviews section — star rating distribution bars + review cards
- Breadcrumb and product metadata (SKU, stock status)
When to use it
- Headless commerce storefronts
- Shopify / WooCommerce theme prototypes
- Any product landing page