13 / 15
All 15 in 15 Tailwind Card Grid Layouts
- 01Tailwind Stats Dashboard KPI Grid
- 02Tailwind Hero Photo Collage Grid
- 03Tailwind Pricing Comparison Card Grid
- 04Tailwind Testimonial Wall Grid
- 05Tailwind Editorial Tabloid Bento Layout
- 06Tailwind Featured Article Magazine Grid
- 07Tailwind Asymmetric Bento Box Feature Grid
- 08Tailwind Stratified Bento Grid Layout
- 09Tailwind Equal-Height Product Grid
- 10Tailwind Fluid Masonry Card Grid
- 11Tailwind Auto-Fit Blog Archive Grid
- 12Tailwind Team Directory Face Grid
- 13Tailwind 3D Hover Flip Card Grid
- 14Tailwind Use-Case Showcase Card Grid
- 15Tailwind Glass Bento Grid Layout
Tailwind 3D Hover Flip Card Grid
A flashcard wall where hovering a tile rotates it 180° to reveal an accent-coloured back face of metrics, and the grid never shifts because only a transform animates. v4 ships perspective-*, transform-3d, backface-hidden and rotate-y-* as real utilities, so the entire 3D scene is expressible without touching the stylesheet.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tcg-06 w-full min-h-svh bg-tcg06-bg font-tcg06-sans text-white px-6 py-12" aria-label="3D hover flip card grid demo">
<div class="max-w-[780px] mx-auto grid [grid-template-columns:repeat(auto-fit,minmax(160px,1fr))] gap-5">
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 rotate-y-180 group-hover:rotate-y-0 group-focus-within:rotate-y-0 motion-reduce:transition-none motion-reduce:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Latency</span>
<h3 class="text-[1.2rem] font-bold">API</h3>
</div>
<div style="--accent:#7c6af7" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">42ms</span>
<p class="text-[.78rem] font-semibold opacity-80">p95 response time</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Uptime</span>
<h3 class="text-[1.2rem] font-bold">SLA</h3>
</div>
<div style="--accent:#00c2a8" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">99.98%</span>
<p class="text-[.78rem] font-semibold opacity-80">last 90 days</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Throughput</span>
<h3 class="text-[1.2rem] font-bold">Requests</h3>
</div>
<div style="--accent:#ff6b9d" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">12k/s</span>
<p class="text-[.78rem] font-semibold opacity-80">peak sustained</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Storage</span>
<h3 class="text-[1.2rem] font-bold">Database</h3>
</div>
<div style="--accent:#ffb454" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">4.2TB</span>
<p class="text-[.78rem] font-semibold opacity-80">across all shards</p>
</div>
</div>
</div>
</div>
</section><section class="tcg-06 w-full min-h-svh bg-tcg06-bg font-tcg06-sans text-white px-6 py-12" aria-label="3D hover flip card grid demo">
<div class="max-w-[780px] mx-auto grid [grid-template-columns:repeat(auto-fit,minmax(160px,1fr))] gap-5">
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 rotate-y-180 group-hover:rotate-y-0 group-focus-within:rotate-y-0 motion-reduce:transition-none motion-reduce:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Latency</span>
<h3 class="text-[1.2rem] font-bold">API</h3>
</div>
<div style="--accent:#7c6af7" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">42ms</span>
<p class="text-[.78rem] font-semibold opacity-80">p95 response time</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Uptime</span>
<h3 class="text-[1.2rem] font-bold">SLA</h3>
</div>
<div style="--accent:#00c2a8" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">99.98%</span>
<p class="text-[.78rem] font-semibold opacity-80">last 90 days</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Throughput</span>
<h3 class="text-[1.2rem] font-bold">Requests</h3>
</div>
<div style="--accent:#ff6b9d" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">12k/s</span>
<p class="text-[.78rem] font-semibold opacity-80">peak sustained</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Storage</span>
<h3 class="text-[1.2rem] font-bold">Database</h3>
</div>
<div style="--accent:#ffb454" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">4.2TB</span>
<p class="text-[.78rem] font-semibold opacity-80">across all shards</p>
</div>
</div>
</div>
</div>
</section>@import "tailwindcss";
@theme {
--color-tcg06-bg: #0d0d12;
--color-tcg06-bg: oklch(0.15 0.006 285);
--color-tcg06-face: #16161d;
--color-tcg06-face: oklch(0.2 0.008 285);
--color-tcg06-dim: #a1a1b0;
--color-tcg06-dim: oklch(0.72 0.01 285);
--font-tcg06-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tcg06: cubic-bezier(0.4, 0.2, 0.2, 1);
}
/* Root rule: the full-bleed contract. */
.tcg-06 {
width: 100%;
min-height: 100svh;
}
.tcg-06 ::selection {
background: #7c6af7;
color: #fff;
}@import "tailwindcss";
@theme {
--color-tcg06-bg: #0d0d12;
--color-tcg06-bg: oklch(0.15 0.006 285);
--color-tcg06-face: #16161d;
--color-tcg06-face: oklch(0.2 0.008 285);
--color-tcg06-dim: #a1a1b0;
--color-tcg06-dim: oklch(0.72 0.01 285);
--font-tcg06-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tcg06: cubic-bezier(0.4, 0.2, 0.2, 1);
}
/* Root rule: the full-bleed contract. */
.tcg-06 {
width: 100%;
min-height: 100svh;
}
.tcg-06 ::selection {
background: #7c6af7;
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-tcg06-bg: #0d0d12;
--color-tcg06-bg: oklch(0.15 0.006 285);
--color-tcg06-face: #16161d;
--color-tcg06-face: oklch(0.2 0.008 285);
--color-tcg06-dim: #a1a1b0;
--color-tcg06-dim: oklch(0.72 0.01 285);
--font-tcg06-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tcg06: cubic-bezier(0.4, 0.2, 0.2, 1);
}
/* Root rule: the full-bleed contract. */
.tcg-06 {
width: 100%;
min-height: 100svh;
}
.tcg-06 ::selection {
background: #7c6af7;
color: #fff;
}@import "tailwindcss";
@theme {
--color-tcg06-bg: #0d0d12;
--color-tcg06-bg: oklch(0.15 0.006 285);
--color-tcg06-face: #16161d;
--color-tcg06-face: oklch(0.2 0.008 285);
--color-tcg06-dim: #a1a1b0;
--color-tcg06-dim: oklch(0.72 0.01 285);
--font-tcg06-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tcg06: cubic-bezier(0.4, 0.2, 0.2, 1);
}
/* Root rule: the full-bleed contract. */
.tcg-06 {
width: 100%;
min-height: 100svh;
}
.tcg-06 ::selection {
background: #7c6af7;
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&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&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- v4 turned the whole 3D vocabulary into utilities —
perspective-*,transform-3d,backface-hidden,rotate-y-*— so this demo's stylesheet holds only tokens. In v3 most of it would still have been arbitrary properties. .flip:hover .innerbecomesgroup-hover:rotate-y-180on the inner: the transform is declared on the element that transforms, which is the port's recurring theme.- The idle-flipped first card was two CSS rules keyed to
:first-child; it is now two utilities on that card, and moving the behaviour to a different card is a copy-paste. group-focus-within:was added alongside every hover so a keyboard user reaching the card's contents sees the same flip — the CSS version had no keyboard path at all.- The per-face
--accentstays inline: four instance colours, each read once. motion-reduce:cancels the rotation instead of slowing it, and because both faces are in the DOM the metric remains reachable either way.
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 3D Hover Flip Card Grid
Source: https://codefronts.com/layouts/tailwind-card-grid-layout/tailwind-3d-hover-flip-card-grid/
A flashcard wall where hovering a tile rotates it 180° to reveal an accent-coloured back face of metrics, and the grid never shifts because only a transform animates. v4 ships perspective-*, transform-3d, backface-hidden and rotate-y-* as real utilities, so the entire 3D scene is expressible without touching the stylesheet.
## HTML
```html
<section class="tcg-06 w-full min-h-svh bg-tcg06-bg font-tcg06-sans text-white px-6 py-12" aria-label="3D hover flip card grid demo">
<div class="max-w-[780px] mx-auto grid [grid-template-columns:repeat(auto-fit,minmax(160px,1fr))] gap-5">
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 rotate-y-180 group-hover:rotate-y-0 group-focus-within:rotate-y-0 motion-reduce:transition-none motion-reduce:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Latency</span>
<h3 class="text-[1.2rem] font-bold">API</h3>
</div>
<div style="--accent:#7c6af7" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">42ms</span>
<p class="text-[.78rem] font-semibold opacity-80">p95 response time</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Uptime</span>
<h3 class="text-[1.2rem] font-bold">SLA</h3>
</div>
<div style="--accent:#00c2a8" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">99.98%</span>
<p class="text-[.78rem] font-semibold opacity-80">last 90 days</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Throughput</span>
<h3 class="text-[1.2rem] font-bold">Requests</h3>
</div>
<div style="--accent:#ff6b9d" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">12k/s</span>
<p class="text-[.78rem] font-semibold opacity-80">peak sustained</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Storage</span>
<h3 class="text-[1.2rem] font-bold">Database</h3>
</div>
<div style="--accent:#ffb454" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">4.2TB</span>
<p class="text-[.78rem] font-semibold opacity-80">across all shards</p>
</div>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcg06-bg: #0d0d12;
--color-tcg06-bg: oklch(0.15 0.006 285);
--color-tcg06-face: #16161d;
--color-tcg06-face: oklch(0.2 0.008 285);
--color-tcg06-dim: #a1a1b0;
--color-tcg06-dim: oklch(0.72 0.01 285);
--font-tcg06-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tcg06: cubic-bezier(0.4, 0.2, 0.2, 1);
}
/* Root rule: the full-bleed contract. */
.tcg-06 {
width: 100%;
min-height: 100svh;
}
.tcg-06 ::selection {
background: #7c6af7;
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 3D Hover Flip Card Grid
Source: https://codefronts.com/layouts/tailwind-card-grid-layout/tailwind-3d-hover-flip-card-grid/
A flashcard wall where hovering a tile rotates it 180° to reveal an accent-coloured back face of metrics, and the grid never shifts because only a transform animates. v4 ships perspective-*, transform-3d, backface-hidden and rotate-y-* as real utilities, so the entire 3D scene is expressible without touching the stylesheet.
## HTML
```html
<section class="tcg-06 w-full min-h-svh bg-tcg06-bg font-tcg06-sans text-white px-6 py-12" aria-label="3D hover flip card grid demo">
<div class="max-w-[780px] mx-auto grid [grid-template-columns:repeat(auto-fit,minmax(160px,1fr))] gap-5">
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 rotate-y-180 group-hover:rotate-y-0 group-focus-within:rotate-y-0 motion-reduce:transition-none motion-reduce:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Latency</span>
<h3 class="text-[1.2rem] font-bold">API</h3>
</div>
<div style="--accent:#7c6af7" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">42ms</span>
<p class="text-[.78rem] font-semibold opacity-80">p95 response time</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Uptime</span>
<h3 class="text-[1.2rem] font-bold">SLA</h3>
</div>
<div style="--accent:#00c2a8" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">99.98%</span>
<p class="text-[.78rem] font-semibold opacity-80">last 90 days</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Throughput</span>
<h3 class="text-[1.2rem] font-bold">Requests</h3>
</div>
<div style="--accent:#ff6b9d" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">12k/s</span>
<p class="text-[.78rem] font-semibold opacity-80">peak sustained</p>
</div>
</div>
</div>
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600 ease-tcg06 group-hover:rotate-y-180 group-focus-within:rotate-y-180 motion-reduce:transition-none motion-reduce:group-hover:rotate-y-0">
<div class="absolute inset-0 flex flex-col justify-end gap-1.5 p-4.5 rounded-[14px] backface-hidden border border-white/10 bg-tcg06-face">
<span class="text-[.7rem] font-semibold tracking-[0.1em] uppercase text-tcg06-dim">Storage</span>
<h3 class="text-[1.2rem] font-bold">Database</h3>
</div>
<div style="--accent:#ffb454" class="absolute inset-0 flex flex-col justify-end gap-1 p-4.5 rounded-[14px] backface-hidden rotate-y-180 bg-[var(--accent)] text-tcg06-bg">
<span class="text-[1.5rem] font-extrabold tracking-[-0.02em] tabular-nums">4.2TB</span>
<p class="text-[.78rem] font-semibold opacity-80">across all shards</p>
</div>
</div>
</div>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcg06-bg: #0d0d12;
--color-tcg06-bg: oklch(0.15 0.006 285);
--color-tcg06-face: #16161d;
--color-tcg06-face: oklch(0.2 0.008 285);
--color-tcg06-dim: #a1a1b0;
--color-tcg06-dim: oklch(0.72 0.01 285);
--font-tcg06-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tcg06: cubic-bezier(0.4, 0.2, 0.2, 1);
}
/* Root rule: the full-bleed contract. */
.tcg-06 {
width: 100%;
min-height: 100svh;
}
.tcg-06 ::selection {
background: #7c6af7;
color: #fff;
}
```How this works
Three layers, all in utilities. The wrapper owns the scene, the inner owns the rotation, the faces own their hiding:
<div class="group perspective-[1000px] h-40">
<div class="relative w-full h-full transform-3d transition-transform duration-600
group-hover: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 must be on the wrapper, not the rotating element — on the inner it flattens the flip into a squash. That constraint is unchanged from CSS; what changes is that the mistake is now visible in the class list rather than buried in a rule.
The hover moved to the group. The CSS version wrote .flip:hover .inner { transform: rotateY(180deg) }. Here the rotation is declared on the element that rotates, keyed to the wrapper's hover. One rule becomes one utility, on the right element.
The idle-flipped tile is two utilities. The first card rests on its back face so the pattern is legible without hovering: rotate-y-180 group-hover:rotate-y-0. The CSS needed a :first-child rule plus a :first-child:hover rule; the tile now simply says what it does.
Hover-only is not enough on touch. Both faces stay in the DOM, so the metric is always available to a screen reader, and the note in the gotchas is the same one the CSS version carried: a production build wants a tap-to-flip state, which in v4 is data-flipped plus group-data-flipped:rotate-y-180.
Make it yours
- Flip on the X axis by swapping every
rotate-y-*forrotate-x-*on the inner and the back face together. - Change the tempo with
duration-*and anease-*token; clip-free rotation tolerates overshoot curves well. - Recolour any back face through its inline
--accent. - Convert to tap-to-flip: put
data-flippedon the wrapper from a five-line controller and addgroup-data-flipped:rotate-y-180beside the hover variant. - Add a pre-flip lift with
group-hover:-translate-y-0.5on the wrapper for a two-stage micro-interaction.
Gotchas — read before shipping
perspective-*belongs on the wrapper. On the rotating element itself the 3D depth collapses and you get a flat squash.- Both faces need
backface-hidden. Omit it on either and the mirrored reverse of the other face shows through as garbled text mid-flip. - The wrapper needs an explicit height (
h-40): the faces are absolutely positioned, so without it the tile has no height at all. - Hover-only flips are unavailable on touch devices. Keep both faces in the DOM (they are) and add a tap state for production.
- Safari benefits from the
-webkit-backface prefix, which Tailwind emits forbackface-hiddenautomatically — one fewer thing to hand-write than in the CSS version.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
3D transforms are broadly supported and run far back; v4's transform-3d, perspective-* and rotate-y-* utilities require Tailwind v4 itself, hence the floor quoted.