UI Components Easy
Product Card
E-commerce product card with image, quick-add to cart, wishlist toggle, discount badge, color swatch picker, and rating stars. Pure CSS + JS.
Open in Lab
MCP
css vanilla-js
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--bg: #0f1117;
--surface: #16181f;
--surface2: #1e2130;
--border: #2a2d3a;
--text: #e2e8f0;
--text-muted: #64748b;
--accent: #818cf8;
--green: #34d399;
--red: #f87171;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
padding: 40px 24px;
}
.page-wrap {
max-width: 960px;
margin: 0 auto;
}
.page-title {
font-size: 1.8rem;
font-weight: 800;
margin-bottom: 6px;
}
.page-sub {
color: var(--text-muted);
font-size: 0.875rem;
margin-bottom: 36px;
}
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 20px;
}
/* Card */
.product-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
overflow: visible;
position: relative;
transition: border-color .2s;
}
.product-card:hover {
border-color: #383b50;
}
/* Image */
.product-image {
height: 200px;
border-radius: 12px 12px 0 0;
display: grid;
place-items: center;
position: relative;
overflow: hidden;
}
.product-img-label {
font-size: 2.8rem;
}
.product-img-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.45);
display: grid;
place-items: center;
opacity: 0;
transition: opacity .2s;
}
.product-card:hover .product-img-overlay {
opacity: 1;
}
.quick-add-btn {
background: #fff;
color: #0f1117;
border: none;
border-radius: 8px;
font-size: 0.82rem;
font-weight: 700;
padding: 9px 18px;
cursor: pointer;
font-family: inherit;
transition: transform .15s;
}
.quick-add-btn:hover {
transform: scale(1.05);
}
.quick-add-btn.success {
background: var(--green);
color: #0f1117;
}
/* Wishlist */
.wishlist-btn {
position: absolute;
top: 10px;
right: 10px;
width: 32px;
height: 32px;
border-radius: 50%;
background: rgba(0, 0, 0, 0.55);
border: none;
color: var(--text-muted);
cursor: pointer;
display: grid;
place-items: center;
transition: background .15s, color .15s;
z-index: 2;
}
.wishlist-btn:hover {
background: rgba(0, 0, 0, 0.75);
color: var(--red);
}
.wishlist-btn[data-wishlisted="true"] {
color: var(--red);
background: rgba(248, 113, 113, 0.2);
}
.wishlist-btn[data-wishlisted="true"] svg {
fill: var(--red);
stroke: var(--red);
}
/* Sale badge */
.sale-badge {
position: absolute;
top: 10px;
left: 10px;
background: var(--red);
color: #fff;
font-size: 0.65rem;
font-weight: 800;
padding: 3px 8px;
border-radius: 999px;
z-index: 2;
}
/* Info */
.product-info {
padding: 16px;
}
.swatches {
display: flex;
gap: 6px;
margin-bottom: 10px;
}
.swatch {
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid transparent;
cursor: pointer;
transition: border-color .15s;
}
.swatch--active {
border-color: #fff;
}
.swatch:hover {
border-color: rgba(255, 255, 255, 0.6);
}
.product-name {
font-size: 0.9rem;
font-weight: 700;
margin-bottom: 6px;
}
.product-rating {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 10px;
}
.stars {
color: #f59e0b;
font-size: 0.85rem;
}
.rating-count {
font-size: 0.72rem;
color: var(--text-muted);
}
.product-price-row {
display: flex;
align-items: baseline;
gap: 8px;
margin-bottom: 12px;
}
.price-now {
font-size: 1.2rem;
font-weight: 800;
}
.price-was {
font-size: 0.82rem;
color: var(--text-muted);
text-decoration: line-through;
}
.add-to-cart-btn {
width: 100%;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-size: 0.82rem;
font-weight: 600;
padding: 9px;
cursor: pointer;
font-family: inherit;
display: flex;
align-items: center;
justify-content: center;
gap: 7px;
transition: background .15s, border-color .15s;
}
.add-to-cart-btn:hover {
border-color: var(--accent);
color: var(--accent);
}
.add-to-cart-btn.success {
background: rgba(52, 211, 153, 0.1);
border-color: var(--green);
color: var(--green);
}
@keyframes cartBounce {
0%,
100% {
transform: none;
}
50% {
transform: scale(1.3);
}
}
.add-to-cart-btn.success .cart-icon {
animation: cartBounce .4s ease;
}// Wishlist toggles
document.querySelectorAll(".wishlist-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const wishlisted = btn.dataset.wishlisted === "true";
btn.dataset.wishlisted = String(!wishlisted);
btn.setAttribute("aria-label", wishlisted ? "Add to wishlist" : "Remove from wishlist");
});
});
// Swatch selection
document.querySelectorAll(".product-card").forEach((card) => {
card.querySelectorAll(".swatch").forEach((swatch) => {
swatch.addEventListener("click", () => {
card.querySelectorAll(".swatch").forEach((s) => s.classList.remove("swatch--active"));
swatch.classList.add("swatch--active");
});
});
});
// Add to cart
document.querySelectorAll(".add-to-cart-btn").forEach((btn) => {
btn.addEventListener("click", () => {
btn.classList.add("success");
btn.innerHTML = `
<svg class="cart-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="20 6 9 17 4 12"/>
</svg>
Added!
`;
setTimeout(() => {
btn.classList.remove("success");
btn.innerHTML = `
<svg class="cart-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M6 2 3 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>
Add to Cart
`;
}, 1800);
});
});
// Quick add
document.querySelectorAll(".quick-add-btn").forEach((btn) => {
btn.addEventListener("click", () => {
btn.textContent = "✓ Added!";
btn.classList.add("success");
setTimeout(() => {
btn.textContent = "Quick Add";
btn.classList.remove("success");
}, 1800);
});
});<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Product Card</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="page-wrap">
<h1 class="page-title">Product Card</h1>
<p class="page-sub">Hover over the cards to reveal quick-add actions.</p>
<div class="product-grid">
<!-- Card 1: Standard -->
<div class="product-card" id="card1">
<div class="product-image" style="background: linear-gradient(135deg,#818cf8,#6366f1)">
<span class="product-img-label">PRO</span>
<div class="product-img-overlay">
<button class="quick-add-btn" data-card="card1">Quick Add</button>
</div>
</div>
<button class="wishlist-btn" aria-label="Add to wishlist" data-wishlisted="false">
<svg width="17" height="17" 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.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z" />
</svg>
</button>
<span class="sale-badge">-20%</span>
<div class="product-info">
<div class="swatches">
<button class="swatch swatch--active" style="background:#818cf8" aria-label="Indigo"></button>
<button class="swatch" style="background:#34d399" aria-label="Green"></button>
<button class="swatch" style="background:#f87171" aria-label="Red"></button>
</div>
<h2 class="product-name">Pro UI Component Pack</h2>
<div class="product-rating">
<span class="stars">★★★★★</span>
<span class="rating-count">(128)</span>
</div>
<div class="product-price-row">
<span class="price-now">$29</span>
<span class="price-was">$36</span>
</div>
<button class="add-to-cart-btn" data-card="card1">
<svg class="cart-icon" width="15" height="15" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<path d="M6 2 3 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>
Add to Cart
</button>
</div>
</div>
<!-- Card 2 -->
<div class="product-card" id="card2">
<div class="product-image" style="background: linear-gradient(135deg,#f59e0b,#fb923c)">
<span class="product-img-label">⚡</span>
<div class="product-img-overlay">
<button class="quick-add-btn" data-card="card2">Quick Add</button>
</div>
</div>
<button class="wishlist-btn" aria-label="Add to wishlist" data-wishlisted="false">
<svg width="17" height="17" 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.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z" />
</svg>
</button>
<div class="product-info">
<div class="swatches">
<button class="swatch swatch--active" style="background:#f59e0b" aria-label="Amber"></button>
<button class="swatch" style="background:#06b6d4" aria-label="Cyan"></button>
</div>
<h2 class="product-name">GSAP Animation Bundle</h2>
<div class="product-rating">
<span class="stars">★★★★☆</span>
<span class="rating-count">(64)</span>
</div>
<div class="product-price-row">
<span class="price-now">$49</span>
</div>
<button class="add-to-cart-btn" data-card="card2">
<svg class="cart-icon" width="15" height="15" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<path d="M6 2 3 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>
Add to Cart
</button>
</div>
</div>
<!-- Card 3 -->
<div class="product-card" id="card3">
<div class="product-image" style="background: linear-gradient(135deg,#34d399,#059669)">
<span class="product-img-label">🚀</span>
<div class="product-img-overlay">
<button class="quick-add-btn" data-card="card3">Quick Add</button>
</div>
</div>
<button class="wishlist-btn" aria-label="Add to wishlist" data-wishlisted="false">
<svg width="17" height="17" 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.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78z" />
</svg>
</button>
<span class="sale-badge">-30%</span>
<div class="product-info">
<div class="swatches">
<button class="swatch swatch--active" style="background:#34d399" aria-label="Green"></button>
</div>
<h2 class="product-name">React Starter Kit</h2>
<div class="product-rating">
<span class="stars">★★★★★</span>
<span class="rating-count">(312)</span>
</div>
<div class="product-price-row">
<span class="price-now">$19</span>
<span class="price-was">$27</span>
</div>
<button class="add-to-cart-btn" data-card="card3">
<svg class="cart-icon" width="15" height="15" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<path d="M6 2 3 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>
Add to Cart
</button>
</div>
</div>
</div>
</main>
<script src="script.js"></script>
</body>
</html>Product Card
A polished e-commerce product card with hover-reveal quick-add button, wishlist heart toggle, sale badge, color swatch selector, star rating display, and a cart count feedback animation.
Features
- Product image with overlay “Quick Add” button revealed on hover
- Wishlist heart button with filled/empty toggle animation
- Sale/discount badge (e.g., “20% OFF”)
- Color swatch selector with active ring indicator
- Star rating (0–5 half-star support) with review count
- “Add to Cart” with a brief cart icon bounce success state
- Multiple card variants: standard, horizontal, minimal
How it works
- Image
::afteroverlay transitions fromopacity: 0to1on.card:hover - Heart button toggles
data-wishlistedattribute + CSS.activefor fill animation - Swatch clicks update
data-selectedand the displayed image alt/gradient - Add-to-cart animates a cart icon via CSS
@keyframes bouncefor 600 ms