02 / 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
Pure-Tailwind 3D Flip Product Card with Spec Back
The apparel-and-gadget flip, and v4 finally has first-class utilities for it: transform-3d, rotate-y-180 and backface-hidden are real classes now, so the whole effect is four utilities and one arbitrary perspective. Hover or focus the card and it turns to a dark spec sheet with a real Add-to-Cart on the back.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tpc-02 w-full min-h-svh grid place-items-center bg-tpc02-bg font-tpc02-sans px-5 py-12" aria-label="3D flip product card demo">
<article tabindex="0" aria-label="Trailhead GTX running shoe — hover or focus the card to read the specifications" class="group w-75 max-w-full [perspective:1100px] rounded-[18px] focus-visible:outline-3 focus-visible:outline-tpc02-acc focus-visible:outline-offset-4 focus:outline-none">
<div class="relative h-105 transform-3d transition-transform duration-700 ease-tpc02 group-hover:rotate-y-180 group-focus-visible:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0 motion-reduce:group-focus-visible:rotate-y-0">
<div class="absolute inset-0 flex flex-col rounded-[18px] overflow-hidden backface-hidden bg-tpc02-card shadow-[0_18px_44px_-22px_oklch(0.25_0.03_150/0.5)] motion-reduce:group-hover:opacity-0 motion-reduce:group-focus-visible:opacity-0">
<div role="img" aria-label="Product photo: Trailhead GTX running shoe" class="flex-1 bg-linear-[135deg,var(--color-tpc02-ph),var(--color-tpc02-ph2)] bg-[url('https://images.unsplash.com/photo-1600185365926-3a2ce3cdb9eb?w=700&h=700&fit=crop&q=70')] bg-cover bg-center"></div>
<div class="flex items-baseline justify-between px-5 pt-4 pb-1">
<h3 class="text-[18px] font-semibold text-tpc02-ink">Trailhead GTX</h3>
<p class="text-[18px] font-bold text-tpc02-acc tabular-nums">$189</p>
</div>
<p class="px-5 pb-4.5 font-tpc02-mono text-[11px] tracking-[0.16em] uppercase text-tpc02-dim">hover / focus to flip</p>
</div>
<div class="absolute inset-0 flex flex-col gap-4 rounded-[18px] overflow-hidden backface-hidden rotate-y-180 bg-tpc02-back text-tpc02-backink px-6 py-6.5 shadow-[0_18px_44px_-22px_oklch(0.25_0.03_150/0.5)]">
<h3 class="text-[18px] font-semibold text-tpc02-backhi">Specifications</h3>
<ul class="flex-1 flex flex-col gap-2.5 list-none">
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Upper</span><b class="font-semibold text-tpc02-backhi">Recycled knit</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Drop</span><b class="font-semibold text-tpc02-backhi">8 mm</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Weight</span><b class="font-semibold text-tpc02-backhi">272 g</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Grip</span><b class="font-semibold text-tpc02-backhi">Vibram® Megagrip</b></li>
</ul>
<button type="button" class="min-h-11 h-11.5 rounded-xl border-0 bg-tpc02-acc text-tpc02-onacc text-[15px] font-semibold cursor-pointer transition-colors duration-200 hover:bg-tpc02-acc2 focus-visible:outline-3 focus-visible:outline-tpc02-ring focus-visible:outline-offset-2 motion-reduce:transition-none">Add to Cart</button>
</div>
</div>
</article>
</section><section class="tpc-02 w-full min-h-svh grid place-items-center bg-tpc02-bg font-tpc02-sans px-5 py-12" aria-label="3D flip product card demo">
<article tabindex="0" aria-label="Trailhead GTX running shoe — hover or focus the card to read the specifications" class="group w-75 max-w-full [perspective:1100px] rounded-[18px] focus-visible:outline-3 focus-visible:outline-tpc02-acc focus-visible:outline-offset-4 focus:outline-none">
<div class="relative h-105 transform-3d transition-transform duration-700 ease-tpc02 group-hover:rotate-y-180 group-focus-visible:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0 motion-reduce:group-focus-visible:rotate-y-0">
<div class="absolute inset-0 flex flex-col rounded-[18px] overflow-hidden backface-hidden bg-tpc02-card shadow-[0_18px_44px_-22px_oklch(0.25_0.03_150/0.5)] motion-reduce:group-hover:opacity-0 motion-reduce:group-focus-visible:opacity-0">
<div role="img" aria-label="Product photo: Trailhead GTX running shoe" class="flex-1 bg-linear-[135deg,var(--color-tpc02-ph),var(--color-tpc02-ph2)] bg-[url('https://images.unsplash.com/photo-1600185365926-3a2ce3cdb9eb?w=700&h=700&fit=crop&q=70')] bg-cover bg-center"></div>
<div class="flex items-baseline justify-between px-5 pt-4 pb-1">
<h3 class="text-[18px] font-semibold text-tpc02-ink">Trailhead GTX</h3>
<p class="text-[18px] font-bold text-tpc02-acc tabular-nums">$189</p>
</div>
<p class="px-5 pb-4.5 font-tpc02-mono text-[11px] tracking-[0.16em] uppercase text-tpc02-dim">hover / focus to flip</p>
</div>
<div class="absolute inset-0 flex flex-col gap-4 rounded-[18px] overflow-hidden backface-hidden rotate-y-180 bg-tpc02-back text-tpc02-backink px-6 py-6.5 shadow-[0_18px_44px_-22px_oklch(0.25_0.03_150/0.5)]">
<h3 class="text-[18px] font-semibold text-tpc02-backhi">Specifications</h3>
<ul class="flex-1 flex flex-col gap-2.5 list-none">
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Upper</span><b class="font-semibold text-tpc02-backhi">Recycled knit</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Drop</span><b class="font-semibold text-tpc02-backhi">8 mm</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Weight</span><b class="font-semibold text-tpc02-backhi">272 g</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Grip</span><b class="font-semibold text-tpc02-backhi">Vibram® Megagrip</b></li>
</ul>
<button type="button" class="min-h-11 h-11.5 rounded-xl border-0 bg-tpc02-acc text-tpc02-onacc text-[15px] font-semibold cursor-pointer transition-colors duration-200 hover:bg-tpc02-acc2 focus-visible:outline-3 focus-visible:outline-tpc02-ring focus-visible:outline-offset-2 motion-reduce:transition-none">Add to Cart</button>
</div>
</div>
</article>
</section>@import "tailwindcss";
@theme {
--color-tpc02-bg: #eef1ee;
--color-tpc02-bg: oklch(0.95 0.008 150);
--color-tpc02-card: #ffffff;
--color-tpc02-card: oklch(1 0 0);
--color-tpc02-ink: #101c14;
--color-tpc02-ink: oklch(0.22 0.03 155);
--color-tpc02-dim: #9aab9c;
--color-tpc02-dim: oklch(0.71 0.02 150);
--color-tpc02-ph: #e3ebe4;
--color-tpc02-ph: oklch(0.93 0.012 150);
--color-tpc02-ph2: #d5e0d6;
--color-tpc02-ph2: oklch(0.89 0.018 150);
--color-tpc02-back: #101c14;
--color-tpc02-back: oklch(0.22 0.03 155);
--color-tpc02-backink: #e7f0e9;
--color-tpc02-backink: oklch(0.94 0.015 155);
--color-tpc02-backhi: #ffffff;
--color-tpc02-backhi: oklch(1 0 0);
--color-tpc02-backdim: #9db6a6;
--color-tpc02-backdim: oklch(0.74 0.03 155);
--color-tpc02-backline: #2a3a30;
--color-tpc02-backline: oklch(0.32 0.02 155);
--color-tpc02-acc: #2f8a52;
--color-tpc02-acc: oklch(0.55 0.15 155);
--color-tpc02-acc2: #277a47;
--color-tpc02-acc2: oklch(0.49 0.14 155);
--color-tpc02-onacc: #ffffff;
--color-tpc02-onacc: oklch(1 0 0);
--color-tpc02-ring: #8fd0a6;
--color-tpc02-ring: oklch(0.8 0.1 155);
--font-tpc02-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc02-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tpc02: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-02 {
width: 100%;
min-height: 100svh;
}
.tpc-02 ::selection {
background: var(--color-tpc02-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tpc02-bg: #eef1ee;
--color-tpc02-bg: oklch(0.95 0.008 150);
--color-tpc02-card: #ffffff;
--color-tpc02-card: oklch(1 0 0);
--color-tpc02-ink: #101c14;
--color-tpc02-ink: oklch(0.22 0.03 155);
--color-tpc02-dim: #9aab9c;
--color-tpc02-dim: oklch(0.71 0.02 150);
--color-tpc02-ph: #e3ebe4;
--color-tpc02-ph: oklch(0.93 0.012 150);
--color-tpc02-ph2: #d5e0d6;
--color-tpc02-ph2: oklch(0.89 0.018 150);
--color-tpc02-back: #101c14;
--color-tpc02-back: oklch(0.22 0.03 155);
--color-tpc02-backink: #e7f0e9;
--color-tpc02-backink: oklch(0.94 0.015 155);
--color-tpc02-backhi: #ffffff;
--color-tpc02-backhi: oklch(1 0 0);
--color-tpc02-backdim: #9db6a6;
--color-tpc02-backdim: oklch(0.74 0.03 155);
--color-tpc02-backline: #2a3a30;
--color-tpc02-backline: oklch(0.32 0.02 155);
--color-tpc02-acc: #2f8a52;
--color-tpc02-acc: oklch(0.55 0.15 155);
--color-tpc02-acc2: #277a47;
--color-tpc02-acc2: oklch(0.49 0.14 155);
--color-tpc02-onacc: #ffffff;
--color-tpc02-onacc: oklch(1 0 0);
--color-tpc02-ring: #8fd0a6;
--color-tpc02-ring: oklch(0.8 0.1 155);
--font-tpc02-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc02-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tpc02: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-02 {
width: 100%;
min-height: 100svh;
}
.tpc-02 ::selection {
background: var(--color-tpc02-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-tpc02-bg: #eef1ee;
--color-tpc02-bg: oklch(0.95 0.008 150);
--color-tpc02-card: #ffffff;
--color-tpc02-card: oklch(1 0 0);
--color-tpc02-ink: #101c14;
--color-tpc02-ink: oklch(0.22 0.03 155);
--color-tpc02-dim: #9aab9c;
--color-tpc02-dim: oklch(0.71 0.02 150);
--color-tpc02-ph: #e3ebe4;
--color-tpc02-ph: oklch(0.93 0.012 150);
--color-tpc02-ph2: #d5e0d6;
--color-tpc02-ph2: oklch(0.89 0.018 150);
--color-tpc02-back: #101c14;
--color-tpc02-back: oklch(0.22 0.03 155);
--color-tpc02-backink: #e7f0e9;
--color-tpc02-backink: oklch(0.94 0.015 155);
--color-tpc02-backhi: #ffffff;
--color-tpc02-backhi: oklch(1 0 0);
--color-tpc02-backdim: #9db6a6;
--color-tpc02-backdim: oklch(0.74 0.03 155);
--color-tpc02-backline: #2a3a30;
--color-tpc02-backline: oklch(0.32 0.02 155);
--color-tpc02-acc: #2f8a52;
--color-tpc02-acc: oklch(0.55 0.15 155);
--color-tpc02-acc2: #277a47;
--color-tpc02-acc2: oklch(0.49 0.14 155);
--color-tpc02-onacc: #ffffff;
--color-tpc02-onacc: oklch(1 0 0);
--color-tpc02-ring: #8fd0a6;
--color-tpc02-ring: oklch(0.8 0.1 155);
--font-tpc02-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc02-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tpc02: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-02 {
width: 100%;
min-height: 100svh;
}
.tpc-02 ::selection {
background: var(--color-tpc02-acc);
color: #fff;
}@import "tailwindcss";
@theme {
--color-tpc02-bg: #eef1ee;
--color-tpc02-bg: oklch(0.95 0.008 150);
--color-tpc02-card: #ffffff;
--color-tpc02-card: oklch(1 0 0);
--color-tpc02-ink: #101c14;
--color-tpc02-ink: oklch(0.22 0.03 155);
--color-tpc02-dim: #9aab9c;
--color-tpc02-dim: oklch(0.71 0.02 150);
--color-tpc02-ph: #e3ebe4;
--color-tpc02-ph: oklch(0.93 0.012 150);
--color-tpc02-ph2: #d5e0d6;
--color-tpc02-ph2: oklch(0.89 0.018 150);
--color-tpc02-back: #101c14;
--color-tpc02-back: oklch(0.22 0.03 155);
--color-tpc02-backink: #e7f0e9;
--color-tpc02-backink: oklch(0.94 0.015 155);
--color-tpc02-backhi: #ffffff;
--color-tpc02-backhi: oklch(1 0 0);
--color-tpc02-backdim: #9db6a6;
--color-tpc02-backdim: oklch(0.74 0.03 155);
--color-tpc02-backline: #2a3a30;
--color-tpc02-backline: oklch(0.32 0.02 155);
--color-tpc02-acc: #2f8a52;
--color-tpc02-acc: oklch(0.55 0.15 155);
--color-tpc02-acc2: #277a47;
--color-tpc02-acc2: oklch(0.49 0.14 155);
--color-tpc02-onacc: #ffffff;
--color-tpc02-onacc: oklch(1 0 0);
--color-tpc02-ring: #8fd0a6;
--color-tpc02-ring: oklch(0.8 0.1 155);
--font-tpc02-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc02-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tpc02: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-02 {
width: 100%;
min-height: 100svh;
}
.tpc-02 ::selection {
background: var(--color-tpc02-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=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&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=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- This is the demo v4 improved most.
transform-style: preserve-3dandbackface-visibility: hiddenused to need arbitrary properties or a plugin;transform-3dandbackface-hiddenare core utilities now, androtate-y-*exists as an individual transform. perspectivestays arbitrary on purpose. It is a scene property with no sensible design scale, and Tailwind is right not to ship one.- The back face keeps its
rotate-y-180as a static utility, not a variant — it is the resting geometry of that face, not a state. motion-reduce:does real work here: it cancels the rotation and cross-fades the front face out instead, so the spec sheet is still reachable without a 700ms spin.- The CSS original put
perspectiveon the same element as its background and radius; splitting stage from card removed a subtle Safari flattening bug. - Enhancement over the CSS original: a visible focus ring on the wrapper (the original set
outline:noneand gave nothing back) and the hint line moved into a mono micro-label.
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: Pure-Tailwind 3D Flip Product Card with Spec Back
Source: https://codefronts.com/components/tailwind-css-product-cards/tailwind-3d-flip-product-card-spec-back/
The apparel-and-gadget flip, and v4 finally has first-class utilities for it: transform-3d, rotate-y-180 and backface-hidden are real classes now, so the whole effect is four utilities and one arbitrary perspective. Hover or focus the card and it turns to a dark spec sheet with a real Add-to-Cart on the back.
## HTML
```html
<section class="tpc-02 w-full min-h-svh grid place-items-center bg-tpc02-bg font-tpc02-sans px-5 py-12" aria-label="3D flip product card demo">
<article tabindex="0" aria-label="Trailhead GTX running shoe — hover or focus the card to read the specifications" class="group w-75 max-w-full [perspective:1100px] rounded-[18px] focus-visible:outline-3 focus-visible:outline-tpc02-acc focus-visible:outline-offset-4 focus:outline-none">
<div class="relative h-105 transform-3d transition-transform duration-700 ease-tpc02 group-hover:rotate-y-180 group-focus-visible:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0 motion-reduce:group-focus-visible:rotate-y-0">
<div class="absolute inset-0 flex flex-col rounded-[18px] overflow-hidden backface-hidden bg-tpc02-card shadow-[0_18px_44px_-22px_oklch(0.25_0.03_150/0.5)] motion-reduce:group-hover:opacity-0 motion-reduce:group-focus-visible:opacity-0">
<div role="img" aria-label="Product photo: Trailhead GTX running shoe" class="flex-1 bg-linear-[135deg,var(--color-tpc02-ph),var(--color-tpc02-ph2)] bg-[url('https://images.unsplash.com/photo-1600185365926-3a2ce3cdb9eb?w=700&h=700&fit=crop&q=70')] bg-cover bg-center"></div>
<div class="flex items-baseline justify-between px-5 pt-4 pb-1">
<h3 class="text-[18px] font-semibold text-tpc02-ink">Trailhead GTX</h3>
<p class="text-[18px] font-bold text-tpc02-acc tabular-nums">$189</p>
</div>
<p class="px-5 pb-4.5 font-tpc02-mono text-[11px] tracking-[0.16em] uppercase text-tpc02-dim">hover / focus to flip</p>
</div>
<div class="absolute inset-0 flex flex-col gap-4 rounded-[18px] overflow-hidden backface-hidden rotate-y-180 bg-tpc02-back text-tpc02-backink px-6 py-6.5 shadow-[0_18px_44px_-22px_oklch(0.25_0.03_150/0.5)]">
<h3 class="text-[18px] font-semibold text-tpc02-backhi">Specifications</h3>
<ul class="flex-1 flex flex-col gap-2.5 list-none">
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Upper</span><b class="font-semibold text-tpc02-backhi">Recycled knit</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Drop</span><b class="font-semibold text-tpc02-backhi">8 mm</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Weight</span><b class="font-semibold text-tpc02-backhi">272 g</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Grip</span><b class="font-semibold text-tpc02-backhi">Vibram® Megagrip</b></li>
</ul>
<button type="button" class="min-h-11 h-11.5 rounded-xl border-0 bg-tpc02-acc text-tpc02-onacc text-[15px] font-semibold cursor-pointer transition-colors duration-200 hover:bg-tpc02-acc2 focus-visible:outline-3 focus-visible:outline-tpc02-ring focus-visible:outline-offset-2 motion-reduce:transition-none">Add to Cart</button>
</div>
</div>
</article>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpc02-bg: #eef1ee;
--color-tpc02-bg: oklch(0.95 0.008 150);
--color-tpc02-card: #ffffff;
--color-tpc02-card: oklch(1 0 0);
--color-tpc02-ink: #101c14;
--color-tpc02-ink: oklch(0.22 0.03 155);
--color-tpc02-dim: #9aab9c;
--color-tpc02-dim: oklch(0.71 0.02 150);
--color-tpc02-ph: #e3ebe4;
--color-tpc02-ph: oklch(0.93 0.012 150);
--color-tpc02-ph2: #d5e0d6;
--color-tpc02-ph2: oklch(0.89 0.018 150);
--color-tpc02-back: #101c14;
--color-tpc02-back: oklch(0.22 0.03 155);
--color-tpc02-backink: #e7f0e9;
--color-tpc02-backink: oklch(0.94 0.015 155);
--color-tpc02-backhi: #ffffff;
--color-tpc02-backhi: oklch(1 0 0);
--color-tpc02-backdim: #9db6a6;
--color-tpc02-backdim: oklch(0.74 0.03 155);
--color-tpc02-backline: #2a3a30;
--color-tpc02-backline: oklch(0.32 0.02 155);
--color-tpc02-acc: #2f8a52;
--color-tpc02-acc: oklch(0.55 0.15 155);
--color-tpc02-acc2: #277a47;
--color-tpc02-acc2: oklch(0.49 0.14 155);
--color-tpc02-onacc: #ffffff;
--color-tpc02-onacc: oklch(1 0 0);
--color-tpc02-ring: #8fd0a6;
--color-tpc02-ring: oklch(0.8 0.1 155);
--font-tpc02-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc02-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tpc02: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-02 {
width: 100%;
min-height: 100svh;
}
.tpc-02 ::selection {
background: var(--color-tpc02-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: Pure-Tailwind 3D Flip Product Card with Spec Back
Source: https://codefronts.com/components/tailwind-css-product-cards/tailwind-3d-flip-product-card-spec-back/
The apparel-and-gadget flip, and v4 finally has first-class utilities for it: transform-3d, rotate-y-180 and backface-hidden are real classes now, so the whole effect is four utilities and one arbitrary perspective. Hover or focus the card and it turns to a dark spec sheet with a real Add-to-Cart on the back.
## HTML
```html
<section class="tpc-02 w-full min-h-svh grid place-items-center bg-tpc02-bg font-tpc02-sans px-5 py-12" aria-label="3D flip product card demo">
<article tabindex="0" aria-label="Trailhead GTX running shoe — hover or focus the card to read the specifications" class="group w-75 max-w-full [perspective:1100px] rounded-[18px] focus-visible:outline-3 focus-visible:outline-tpc02-acc focus-visible:outline-offset-4 focus:outline-none">
<div class="relative h-105 transform-3d transition-transform duration-700 ease-tpc02 group-hover:rotate-y-180 group-focus-visible:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0 motion-reduce:group-focus-visible:rotate-y-0">
<div class="absolute inset-0 flex flex-col rounded-[18px] overflow-hidden backface-hidden bg-tpc02-card shadow-[0_18px_44px_-22px_oklch(0.25_0.03_150/0.5)] motion-reduce:group-hover:opacity-0 motion-reduce:group-focus-visible:opacity-0">
<div role="img" aria-label="Product photo: Trailhead GTX running shoe" class="flex-1 bg-linear-[135deg,var(--color-tpc02-ph),var(--color-tpc02-ph2)] bg-[url('https://images.unsplash.com/photo-1600185365926-3a2ce3cdb9eb?w=700&h=700&fit=crop&q=70')] bg-cover bg-center"></div>
<div class="flex items-baseline justify-between px-5 pt-4 pb-1">
<h3 class="text-[18px] font-semibold text-tpc02-ink">Trailhead GTX</h3>
<p class="text-[18px] font-bold text-tpc02-acc tabular-nums">$189</p>
</div>
<p class="px-5 pb-4.5 font-tpc02-mono text-[11px] tracking-[0.16em] uppercase text-tpc02-dim">hover / focus to flip</p>
</div>
<div class="absolute inset-0 flex flex-col gap-4 rounded-[18px] overflow-hidden backface-hidden rotate-y-180 bg-tpc02-back text-tpc02-backink px-6 py-6.5 shadow-[0_18px_44px_-22px_oklch(0.25_0.03_150/0.5)]">
<h3 class="text-[18px] font-semibold text-tpc02-backhi">Specifications</h3>
<ul class="flex-1 flex flex-col gap-2.5 list-none">
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Upper</span><b class="font-semibold text-tpc02-backhi">Recycled knit</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Drop</span><b class="font-semibold text-tpc02-backhi">8 mm</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Weight</span><b class="font-semibold text-tpc02-backhi">272 g</b></li>
<li class="flex justify-between pb-2.5 text-sm border-b border-tpc02-backline"><span class="text-tpc02-backdim">Grip</span><b class="font-semibold text-tpc02-backhi">Vibram® Megagrip</b></li>
</ul>
<button type="button" class="min-h-11 h-11.5 rounded-xl border-0 bg-tpc02-acc text-tpc02-onacc text-[15px] font-semibold cursor-pointer transition-colors duration-200 hover:bg-tpc02-acc2 focus-visible:outline-3 focus-visible:outline-tpc02-ring focus-visible:outline-offset-2 motion-reduce:transition-none">Add to Cart</button>
</div>
</div>
</article>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tpc02-bg: #eef1ee;
--color-tpc02-bg: oklch(0.95 0.008 150);
--color-tpc02-card: #ffffff;
--color-tpc02-card: oklch(1 0 0);
--color-tpc02-ink: #101c14;
--color-tpc02-ink: oklch(0.22 0.03 155);
--color-tpc02-dim: #9aab9c;
--color-tpc02-dim: oklch(0.71 0.02 150);
--color-tpc02-ph: #e3ebe4;
--color-tpc02-ph: oklch(0.93 0.012 150);
--color-tpc02-ph2: #d5e0d6;
--color-tpc02-ph2: oklch(0.89 0.018 150);
--color-tpc02-back: #101c14;
--color-tpc02-back: oklch(0.22 0.03 155);
--color-tpc02-backink: #e7f0e9;
--color-tpc02-backink: oklch(0.94 0.015 155);
--color-tpc02-backhi: #ffffff;
--color-tpc02-backhi: oklch(1 0 0);
--color-tpc02-backdim: #9db6a6;
--color-tpc02-backdim: oklch(0.74 0.03 155);
--color-tpc02-backline: #2a3a30;
--color-tpc02-backline: oklch(0.32 0.02 155);
--color-tpc02-acc: #2f8a52;
--color-tpc02-acc: oklch(0.55 0.15 155);
--color-tpc02-acc2: #277a47;
--color-tpc02-acc2: oklch(0.49 0.14 155);
--color-tpc02-onacc: #ffffff;
--color-tpc02-onacc: oklch(1 0 0);
--color-tpc02-ring: #8fd0a6;
--color-tpc02-ring: oklch(0.8 0.1 155);
--font-tpc02-sans: 'Space Grotesk', system-ui, sans-serif;
--font-tpc02-mono: 'JetBrains Mono', ui-monospace, monospace;
--ease-tpc02: cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tpc-02 {
width: 100%;
min-height: 100svh;
}
.tpc-02 ::selection {
background: var(--color-tpc02-acc);
color: #fff;
}
```How this works
The 3D scene, in five utilities.
<div class="group [perspective:1100px]">
<div class="relative h-105 transform-3d transition-transform duration-700
group-hover:rotate-y-180 group-focus-visible:rotate-y-180">
<div class="absolute inset-0 backface-hidden">front</div>
<div class="absolute inset-0 backface-hidden rotate-y-180">back</div>Perspective lives on the outer element and transform-3d on the inner — never both on one box, or the faces flatten and the back shows through.
Focus is on the outer wrapper. The wrapper carries tabindex="0" and an aria-label that says the card flips, because a mouse-only flip hides content from everyone else. group-focus-visible: — not group-focus: — keeps the flip from firing on a stray mouse click.
Both faces are still in the accessibility tree. That is deliberate here: the back is a spec list a screen reader should read. If the back held a duplicate of the front's CTA you would be shipping two identical buttons, which is why the back's button is the only Add-to-Cart in the card.
Make it yours
- Flip axis: swap
rotate-y-180forrotate-x-180on the inner and the back face together — both, or the back reads upside down. - Scene depth: lower
[perspective:1100px]to[perspective:700px]for a much more aggressive turn. - Retheme each face independently —
--color-tpc02-backis the dark spec panel,--color-tpc02-cardthe light front. - Tap-to-flip on mobile: add a
peercheckbox and swap the twogroup-*variants forpeer-checked:rotate-y-180— still zero JS. - Flip tempo:
duration-700plusease-tpc02, defined once in@theme.
Gotchas — read before shipping
- Both faces need
backface-hiddenANDabsolute inset-0. Miss either and the back text ghosts through the front. - The faces need an explicit height (
h-105here) — two absolutely positioned children give the inner element no height of its own. transform-3dand the[perspective:…]must be on different elements; together on one they cancel the depth.- A hover-only flip is a trap on touch. Keep the
tabindex+aria-labelwrapper, or offer the checkbox variant above. - Don't put
overflow-hiddenon the 3D parent: it forces a flat rendering context in some engines and clips the turning face.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
3D transforms and backface-visibility have been supported for a decade; v4's transform-3d, rotate-y-* and backface-hidden utilities are what require the modern floor.