03 / 18
All 18 in 18 Tailwind CSS Product Cards
- 01Tailwind Product Card with Hover-Reveal Add to Cart
- 02Pure-Tailwind 3D Flip Product Card with Spec Back
- 03Tailwind Contained Image Zoom Product Card
- 04Tailwind Mouse-Tracking 3D Tilt Product Card
- 05Tailwind Slide-Up Details Panel Product Card
- 06Tailwind Minimalist eCommerce Product Card
- 07Tailwind Glassmorphism Product Card
- 08Tailwind Dark-Mode Gadget Product Card with Accent Glow
- 09Tailwind Neon Glowing Conic-Border Product Card
- 10Tailwind Material-Design Elevation Product Card
- 11Tailwind Horizontal Product Card with Container Queries
- 12Tailwind Auto-Fit Product Catalog Grid
- 13Tailwind Sale Ribbon & Coupon Badge Product Card
- 14Tailwind Sneaker Card with Colourway & Size Picker
- 15Tailwind Digital Download Product Card
- 16Tailwind Food Delivery Card with Quantity Stepper
- 17Tailwind Real Estate Listing Card with Favourite Toggle
- 18Tailwind Subscription Pricing Card with Billing Switch
Tailwind Contained Image Zoom Product Card
The classic CRO zoom: the photo scales inside its frame while a gradient scrim and a 'Quick view' chip fade up over it — and because the frame is the link, the whole thing is one group with a group-focus-visible: twin on every hover state. The zoom is clipped by overflow-hidden, so nothing ever spills into the neighbouring grid cell.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tpc-03 w-full min-h-svh grid place-items-center bg-tpc03-bg font-tpc03-sans px-5 py-12" aria-label="Contained image zoom product card demo">
<article class="w-75 max-w-full">
<a href="#meridian-tote" aria-label="Quick view: Meridian leather tote" class="group/frame relative block aspect-[4/5] rounded-2xl overflow-hidden no-underline transition-shadow duration-400 shadow-[0_14px_34px_-20px_oklch(0.35_0.06_50/0.5)] hover:shadow-[0_22px_48px_-20px_oklch(0.35_0.06_50/0.55)] focus-visible:outline-3 focus-visible:outline-tpc03-acc focus-visible:outline-offset-3">
<span role="img" aria-label="Product photo: model carrying the Meridian leather tote" class="absolute inset-0 bg-linear-[135deg,var(--color-tpc03-ph),var(--color-tpc03-ph2)] bg-[url('https://images.unsplash.com/photo-1584917865442-de89df76afd3?w=700&h=880&fit=crop&q=70')] bg-cover bg-center transition-transform duration-500 ease-tpc03 group-hover/frame:scale-109 group-focus-visible/frame:scale-109 motion-reduce:transition-none motion-reduce:group-hover/frame:scale-100 motion-reduce:group-focus-visible/frame:scale-100"></span>
<span class="absolute inset-0 flex items-end justify-center pb-5.5 bg-linear-[0deg,oklch(0.28_0.04_50/0.45),transparent_55%] opacity-0 transition-opacity duration-400 group-hover/frame:opacity-100 group-focus-visible/frame:opacity-100 motion-reduce:transition-none">
<span class="px-5 py-2.5 rounded-full bg-tpc03-chip text-tpc03-chipink text-[13px] font-semibold tracking-[0.02em] translate-y-2 transition-transform duration-400 ease-tpc03 group-hover/frame:translate-y-0 group-focus-visible/frame:translate-y-0 motion-reduce:transition-none motion-reduce:translate-y-0">Quick view</span>
</span>
</a>
<div class="flex items-baseline justify-between gap-3 pt-4 px-1">
<h3 class="text-base font-semibold text-tpc03-ink text-pretty">Meridian Leather Tote</h3>
<p class="text-base font-bold text-tpc03-acc tabular-nums">$240</p>
</div>
</article>
</section><section class="tpc-03 w-full min-h-svh grid place-items-center bg-tpc03-bg font-tpc03-sans px-5 py-12" aria-label="Contained image zoom product card demo">
<article class="w-75 max-w-full">
<a href="#meridian-tote" aria-label="Quick view: Meridian leather tote" class="group/frame relative block aspect-[4/5] rounded-2xl overflow-hidden no-underline transition-shadow duration-400 shadow-[0_14px_34px_-20px_oklch(0.35_0.06_50/0.5)] hover:shadow-[0_22px_48px_-20px_oklch(0.35_0.06_50/0.55)] focus-visible:outline-3 focus-visible:outline-tpc03-acc focus-visible:outline-offset-3">
<span role="img" aria-label="Product photo: model carrying the Meridian leather tote" class="absolute inset-0 bg-linear-[135deg,var(--color-tpc03-ph),var(--color-tpc03-ph2)] bg-[url('https://images.unsplash.com/photo-1584917865442-de89df76afd3?w=700&h=880&fit=crop&q=70')] bg-cover bg-center transition-transform duration-500 ease-tpc03 group-hover/frame:scale-109 group-focus-visible/frame:scale-109 motion-reduce:transition-none motion-reduce:group-hover/frame:scale-100 motion-reduce:group-focus-visible/frame:scale-100"></span>
<span class="absolute inset-0 flex items-end justify-center pb-5.5 bg-linear-[0deg,oklch(0.28_0.04_50/0.45),transparent_55%] opacity-0 transition-opacity duration-400 group-hover/frame:opacity-100 group-focus-visible/frame:opacity-100 motion-reduce:transition-none">
<span class="px-5 py-2.5 rounded-full bg-tpc03-chip text-tpc03-chipink text-[13px] font-semibold tracking-[0.02em] translate-y-2 transition-transform duration-400 ease-tpc03 group-hover/frame:translate-y-0 group-focus-visible/frame:translate-y-0 motion-reduce:transition-none motion-reduce:translate-y-0">Quick view</span>
</span>
</a>
<div class="flex items-baseline justify-between gap-3 pt-4 px-1">
<h3 class="text-base font-semibold text-tpc03-ink text-pretty">Meridian Leather Tote</h3>
<p class="text-base font-bold text-tpc03-acc tabular-nums">$240</p>
</div>
</article>
</section>@import "tailwindcss";
@theme {
--color-tpc03-bg: #f6f3ef;
--color-tpc03-bg: oklch(0.96 0.008 70);
--color-tpc03-ink: #2c2115;
--color-tpc03-ink: oklch(0.28 0.03 60);
--color-tpc03-ph: #e8ddd0;
--color-tpc03-ph: oklch(0.91 0.02 70);
--color-tpc03-ph2: #d8c7b3;
--color-tpc03-ph2: oklch(0.84 0.035 70);
--color-tpc03-chip: #fffdfa;
--color-tpc03-chip: oklch(0.99 0.004 70);
--color-tpc03-chipink: #3a2a1a;
--color-tpc03-chipink: oklch(0.3 0.035 55);
--color-tpc03-acc: #8a5a28;
--color-tpc03-acc: oklch(0.47 0.09 55);
--font-tpc03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--ease-tpc03: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-03 {
width: 100%;
min-height: 100svh;
}
.tpc-03 ::selection {
background: var(--color-tpc03-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tpc03-bg: #f6f3ef;
--color-tpc03-bg: oklch(0.96 0.008 70);
--color-tpc03-ink: #2c2115;
--color-tpc03-ink: oklch(0.28 0.03 60);
--color-tpc03-ph: #e8ddd0;
--color-tpc03-ph: oklch(0.91 0.02 70);
--color-tpc03-ph2: #d8c7b3;
--color-tpc03-ph2: oklch(0.84 0.035 70);
--color-tpc03-chip: #fffdfa;
--color-tpc03-chip: oklch(0.99 0.004 70);
--color-tpc03-chipink: #3a2a1a;
--color-tpc03-chipink: oklch(0.3 0.035 55);
--color-tpc03-acc: #8a5a28;
--color-tpc03-acc: oklch(0.47 0.09 55);
--font-tpc03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--ease-tpc03: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-03 {
width: 100%;
min-height: 100svh;
}
.tpc-03 ::selection {
background: var(--color-tpc03-acc);
color: #fff;
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tpc03-bg: #f6f3ef;
--color-tpc03-bg: oklch(0.96 0.008 70);
--color-tpc03-ink: #2c2115;
--color-tpc03-ink: oklch(0.28 0.03 60);
--color-tpc03-ph: #e8ddd0;
--color-tpc03-ph: oklch(0.91 0.02 70);
--color-tpc03-ph2: #d8c7b3;
--color-tpc03-ph2: oklch(0.84 0.035 70);
--color-tpc03-chip: #fffdfa;
--color-tpc03-chip: oklch(0.99 0.004 70);
--color-tpc03-chipink: #3a2a1a;
--color-tpc03-chipink: oklch(0.3 0.035 55);
--color-tpc03-acc: #8a5a28;
--color-tpc03-acc: oklch(0.47 0.09 55);
--font-tpc03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--ease-tpc03: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-03 {
width: 100%;
min-height: 100svh;
}
.tpc-03 ::selection {
background: var(--color-tpc03-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tpc03-bg: #f6f3ef;
--color-tpc03-bg: oklch(0.96 0.008 70);
--color-tpc03-ink: #2c2115;
--color-tpc03-ink: oklch(0.28 0.03 60);
--color-tpc03-ph: #e8ddd0;
--color-tpc03-ph: oklch(0.91 0.02 70);
--color-tpc03-ph2: #d8c7b3;
--color-tpc03-ph2: oklch(0.84 0.035 70);
--color-tpc03-chip: #fffdfa;
--color-tpc03-chip: oklch(0.99 0.004 70);
--color-tpc03-chipink: #3a2a1a;
--color-tpc03-chipink: oklch(0.3 0.035 55);
--color-tpc03-acc: #8a5a28;
--color-tpc03-acc: oklch(0.47 0.09 55);
--font-tpc03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--ease-tpc03: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-03 {
width: 100%;
min-height: 100svh;
}
.tpc-03 ::selection {
background: var(--color-tpc03-acc);
color: #fff;
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- Six hover/focus rules in the CSS version become one named group and three responders. The name (
/frame) is the port's insurance against nesting this card inside a catalog-level group. - The scrim's gradient is a static arbitrary
bg-linear-[…]and onlyopacitytransitions. Transitioning the gradient itself would repaint it every frame. group-focus-visible/frame:appears beside everygroup-hover/frame:. The CSS version repeats:focus-visiblein three comma-joined selectors; here the pairing is local and hard to forget.motion-reduce:keeps the scrim and chip and drops only the scale — a reveal that vanishes entirely would hide the affordance.- Enhancement over the CSS original: the chip's 8px rise, a frame shadow lift, and the name/price row moved to a baseline-aligned two-column row that survives long product names.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Contained Image Zoom Product Card
Source: https://codefronts.com/components/tailwind-css-product-cards/tailwind-contained-image-zoom-product-card/
The classic CRO zoom: the photo scales inside its frame while a gradient scrim and a 'Quick view' chip fade up over it — and because the frame is the link, the whole thing is one group with a group-focus-visible: twin on every hover state. The zoom is clipped by overflow-hidden, so nothing ever spills into the neighbouring grid cell.
## HTML
```html
<section class="tpc-03 w-full min-h-svh grid place-items-center bg-tpc03-bg font-tpc03-sans px-5 py-12" aria-label="Contained image zoom product card demo">
<article class="w-75 max-w-full">
<a href="#meridian-tote" aria-label="Quick view: Meridian leather tote" class="group/frame relative block aspect-[4/5] rounded-2xl overflow-hidden no-underline transition-shadow duration-400 shadow-[0_14px_34px_-20px_oklch(0.35_0.06_50/0.5)] hover:shadow-[0_22px_48px_-20px_oklch(0.35_0.06_50/0.55)] focus-visible:outline-3 focus-visible:outline-tpc03-acc focus-visible:outline-offset-3">
<span role="img" aria-label="Product photo: model carrying the Meridian leather tote" class="absolute inset-0 bg-linear-[135deg,var(--color-tpc03-ph),var(--color-tpc03-ph2)] bg-[url('https://images.unsplash.com/photo-1584917865442-de89df76afd3?w=700&h=880&fit=crop&q=70')] bg-cover bg-center transition-transform duration-500 ease-tpc03 group-hover/frame:scale-109 group-focus-visible/frame:scale-109 motion-reduce:transition-none motion-reduce:group-hover/frame:scale-100 motion-reduce:group-focus-visible/frame:scale-100"></span>
<span class="absolute inset-0 flex items-end justify-center pb-5.5 bg-linear-[0deg,oklch(0.28_0.04_50/0.45),transparent_55%] opacity-0 transition-opacity duration-400 group-hover/frame:opacity-100 group-focus-visible/frame:opacity-100 motion-reduce:transition-none">
<span class="px-5 py-2.5 rounded-full bg-tpc03-chip text-tpc03-chipink text-[13px] font-semibold tracking-[0.02em] translate-y-2 transition-transform duration-400 ease-tpc03 group-hover/frame:translate-y-0 group-focus-visible/frame:translate-y-0 motion-reduce:transition-none motion-reduce:translate-y-0">Quick view</span>
</span>
</a>
<div class="flex items-baseline justify-between gap-3 pt-4 px-1">
<h3 class="text-base font-semibold text-tpc03-ink text-pretty">Meridian Leather Tote</h3>
<p class="text-base font-bold text-tpc03-acc tabular-nums">$240</p>
</div>
</article>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpc03-bg: #f6f3ef;
--color-tpc03-bg: oklch(0.96 0.008 70);
--color-tpc03-ink: #2c2115;
--color-tpc03-ink: oklch(0.28 0.03 60);
--color-tpc03-ph: #e8ddd0;
--color-tpc03-ph: oklch(0.91 0.02 70);
--color-tpc03-ph2: #d8c7b3;
--color-tpc03-ph2: oklch(0.84 0.035 70);
--color-tpc03-chip: #fffdfa;
--color-tpc03-chip: oklch(0.99 0.004 70);
--color-tpc03-chipink: #3a2a1a;
--color-tpc03-chipink: oklch(0.3 0.035 55);
--color-tpc03-acc: #8a5a28;
--color-tpc03-acc: oklch(0.47 0.09 55);
--font-tpc03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--ease-tpc03: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-03 {
width: 100%;
min-height: 100svh;
}
.tpc-03 ::selection {
background: var(--color-tpc03-acc);
color: #fff;
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Contained Image Zoom Product Card
Source: https://codefronts.com/components/tailwind-css-product-cards/tailwind-contained-image-zoom-product-card/
The classic CRO zoom: the photo scales inside its frame while a gradient scrim and a 'Quick view' chip fade up over it — and because the frame is the link, the whole thing is one group with a group-focus-visible: twin on every hover state. The zoom is clipped by overflow-hidden, so nothing ever spills into the neighbouring grid cell.
## HTML
```html
<section class="tpc-03 w-full min-h-svh grid place-items-center bg-tpc03-bg font-tpc03-sans px-5 py-12" aria-label="Contained image zoom product card demo">
<article class="w-75 max-w-full">
<a href="#meridian-tote" aria-label="Quick view: Meridian leather tote" class="group/frame relative block aspect-[4/5] rounded-2xl overflow-hidden no-underline transition-shadow duration-400 shadow-[0_14px_34px_-20px_oklch(0.35_0.06_50/0.5)] hover:shadow-[0_22px_48px_-20px_oklch(0.35_0.06_50/0.55)] focus-visible:outline-3 focus-visible:outline-tpc03-acc focus-visible:outline-offset-3">
<span role="img" aria-label="Product photo: model carrying the Meridian leather tote" class="absolute inset-0 bg-linear-[135deg,var(--color-tpc03-ph),var(--color-tpc03-ph2)] bg-[url('https://images.unsplash.com/photo-1584917865442-de89df76afd3?w=700&h=880&fit=crop&q=70')] bg-cover bg-center transition-transform duration-500 ease-tpc03 group-hover/frame:scale-109 group-focus-visible/frame:scale-109 motion-reduce:transition-none motion-reduce:group-hover/frame:scale-100 motion-reduce:group-focus-visible/frame:scale-100"></span>
<span class="absolute inset-0 flex items-end justify-center pb-5.5 bg-linear-[0deg,oklch(0.28_0.04_50/0.45),transparent_55%] opacity-0 transition-opacity duration-400 group-hover/frame:opacity-100 group-focus-visible/frame:opacity-100 motion-reduce:transition-none">
<span class="px-5 py-2.5 rounded-full bg-tpc03-chip text-tpc03-chipink text-[13px] font-semibold tracking-[0.02em] translate-y-2 transition-transform duration-400 ease-tpc03 group-hover/frame:translate-y-0 group-focus-visible/frame:translate-y-0 motion-reduce:transition-none motion-reduce:translate-y-0">Quick view</span>
</span>
</a>
<div class="flex items-baseline justify-between gap-3 pt-4 px-1">
<h3 class="text-base font-semibold text-tpc03-ink text-pretty">Meridian Leather Tote</h3>
<p class="text-base font-bold text-tpc03-acc tabular-nums">$240</p>
</div>
</article>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpc03-bg: #f6f3ef;
--color-tpc03-bg: oklch(0.96 0.008 70);
--color-tpc03-ink: #2c2115;
--color-tpc03-ink: oklch(0.28 0.03 60);
--color-tpc03-ph: #e8ddd0;
--color-tpc03-ph: oklch(0.91 0.02 70);
--color-tpc03-ph2: #d8c7b3;
--color-tpc03-ph2: oklch(0.84 0.035 70);
--color-tpc03-chip: #fffdfa;
--color-tpc03-chip: oklch(0.99 0.004 70);
--color-tpc03-chipink: #3a2a1a;
--color-tpc03-chipink: oklch(0.3 0.035 55);
--color-tpc03-acc: #8a5a28;
--color-tpc03-acc: oklch(0.47 0.09 55);
--font-tpc03-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
--ease-tpc03: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-03 {
width: 100%;
min-height: 100svh;
}
.tpc-03 ::selection {
background: var(--color-tpc03-acc);
color: #fff;
}
```How this works
The frame is the interactive element. It is an <a>, not a div with a click handler, and it carries the group:
<a class="group/frame relative block aspect-[4/5] overflow-hidden rounded-2xl">
<span class="absolute inset-0 bg-cover transition-transform duration-500
group-hover/frame:scale-109 group-focus-visible/frame:scale-109"></span>Focus lands on the link, so group-focus-visible/frame: lights the same three layers the mouse does. No extra markup, no tabindex.
Why the image and not the card. Scaling the whole card would scale the type and fight the border radius. Scaling only the absolutely positioned image inside a clipped frame keeps text crisp, keeps the layout box fixed, and keeps the work on the compositor — a catalog of forty of these does not reflow on hover.
The enhancement. The original faded a single scrim. Here the chip also rises 8px, and the frame gains a subtle group-hover/frame:shadow lift — two more utilities for a much more deliberate-feeling reveal.
Make it yours
- Zoom depth:
scale-105is subtle,scale-109(here) is the catalog default,scale-115is a feature-card push. - Overlay copy: the chip text — 'Quick view', 'View details', 'Shop the look' — or delete the whole
<span>for a pure zoom. - Zoom origin: add
origin-topororigin-[60%_40%]to push toward a product hotspot. - Frame ratio:
aspect-[4/5]for fashion,aspect-squarefor accessories,aspect-[3/4]for footwear. - Tempo:
duration-500on the image andduration-400on the scrim — the slight mismatch is what makes it feel photographic.
Gotchas — read before shipping
overflow-hiddenon the frame is the whole containment story. Without it the scaled image spills over adjacent cards.- Scale the image, not the card: a scaled card scales its text and its radius clips strangely.
- Give the frame a fixed
aspect-*or rows of cards will jog by a few pixels as images of different dimensions load. - The chip is decoration over a link, so keep it inside the
<a>and out of the accessibility tree's way — the link'saria-labelis what gets announced. - A named group (
group/frame) matters as soon as this card sits inside another group: an unnamedgroup-hover:matches the nearest group, which may not be the frame.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
transform: scale, overflow clipping and aspect-ratio are universally supported; the floor is Tailwind v4's generated sheet.