Home / Snippets / UI Components /
Product card
E-commerce card with image placeholder, star rating, price, and add-to-cart button.
Wireless Headphones
Quick implementation
/* HTML:
<div class="product-card">
<div class="product-card-img"><img src="..." alt="..." /></div>
<div class="product-card-body">
<h3 class="product-card-title">...</h3>
<div class="product-card-rating" aria-label="4 out of 5 stars">...</div>
<div class="product-card-price">$79.99</div>
<button class="product-card-btn">Add to cart</button>
</div>
</div>
*/
.product-card {
display: flex;
flex-direction: column;
width: 18rem;
border-radius: 1rem;
border: 1px solid oklch(0.3 0.02 260);
background: oklch(0.19 0.02 260);
overflow: hidden;
transition: box-shadow 0.25s ease, transform 0.25s ease;
}
.product-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px oklch(0 0 0 / 0.3);
}
.product-card-img {
aspect-ratio: 4 / 3;
overflow: hidden;
}
.product-card-img img {
width: 100%;
height: 100%;
object-fit: cover;
}
.product-card-body {
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
flex: 1;
}
.product-card-title {
font-weight: 600;
font-size: 1rem;
color: oklch(0.93 0.01 260);
}
.product-card-rating {
display: flex;
gap: 0.15rem;
color: oklch(0.78 0.15 85);
font-size: 0.85rem;
}
.product-card-price {
font-weight: 700;
font-size: 1.25rem;
color: oklch(0.93 0.01 260);
}
.product-card-btn {
display: block;
width: 100%;
padding: 0.65rem;
border: none;
border-radius: 0.5rem;
background: oklch(0.52 0.22 265);
color: oklch(1 0 0);
font-weight: 600;
font-size: 0.9rem;
cursor: pointer;
transition: background 0.2s ease;
margin-top: auto;
}
.product-card-btn:hover {
background: oklch(0.45 0.22 265);
}
.product-card-btn:focus-visible {
outline: 2px solid oklch(0.72 0.19 265);
outline-offset: 2px;
}
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior frontend engineer building an e-commerce component library.
Goal: A product card with image, title, star rating, price (with optional sale price), and an add-to-cart button — no JavaScript for layout or styling.
Technical constraints:
- Use flexbox column layout with flex: 1 on the body so the button anchors to the bottom.
- Image area uses aspect-ratio: 4/3 with object-fit: cover.
- Star rating uses Unicode ★ characters colored with oklch(0.78 0.15 85).
- Hover lifts the card with translateY(-2px) and adds a box-shadow transition.
- Use oklch() for all color values, not hex or rgba().
- Button uses :focus-visible for keyboard focus ring with outline-offset.
Framework variant (pick one):
A) Vanilla HTML + CSS only.
B) React component — accept image (string), title (string), rating (number), price (number), salePrice (number | null), and onAddToCart handler props.
Edge cases to handle:
- Long product titles should truncate with text-overflow: ellipsis or wrap gracefully.
- Missing image should show a placeholder background, not a broken icon.
- Rating must have an aria-label like "4 out of 5 stars" for screen readers.
- Card must be keyboard-focusable if the entire card is a link (use <a> wrapper).
Return HTML + CSS.
Why this matters in 2026
The product card is the atomic unit of e-commerce UI. Every storefront, marketplace, and comparison site needs one. Modern CSS handles the entire layout — aspect-ratio for consistent image sizing, flex: 1 for bottom-anchored buttons, oklch() for perceptually uniform colors — without the framework-specific wrappers that made these components brittle in the past.
The logic
The card is a flex-direction: column container. The image area uses aspect-ratio: 4/3 to maintain proportions regardless of the actual image dimensions. The body section gets flex: 1 so it expands to fill remaining space, pushing the add-to-cart button to the bottom via margin-top: auto. The hover effect combines translateY(-2px) with a box-shadow transition — both are compositor-friendly and won't trigger layout recalculation.
Accessibility & performance
The star rating needs an aria-label like "4 out of 5 stars" because Unicode stars are not reliably announced by screen readers. If the entire card should be clickable, wrap the title in an <a> and use a pseudo-element stretch technique rather than wrapping the whole card in a link (which creates a massive accessible name). The overflow: hidden on the card clips the image without requiring JavaScript-based lazy loading boundaries, and transform + box-shadow transitions stay on the GPU compositor thread.