01 / 33
All 33 in 33 Tailwind CSS Card Hover Effects
- 01Tailwind 3D Tilt & Parallax Card with Depth Layers
- 02Tailwind Glowing Gradient & Glassmorphic Border Card
- 03Tailwind Image Zoom Card with Slide-Up Content Reveal
- 04Tailwind 3D Flip Card (Front to Back) with backface-visibility
- 05Tailwind Sibling De-emphasis Pricing Grid with has-hover
- 06Tailwind Minimalist Elevation Card with Layered Dynamic Shadows
- 07Tailwind Elastic Lift Card with Spring Cubic-Bezier
- 08Tailwind Animated Conic Gradient Border Card with @property
- 09Tailwind Stacked Layer Reveal Card (Fanned Risograph Deck)
- 10Tailwind Holographic Foil Shimmer Card with mix-blend-mode
- 11Tailwind Curtain Reveal Card (Parting Velvet Halves)
- 12Tailwind Corner Peel Card with clip-path Triangles
- 13Tailwind Neon Sign Card with Layered Text-Shadow Glow
- 14Tailwind CRT Scanline Terminal Card with Sweeping Beam
- 15Tailwind Depth Parallax Landscape Card with Layer Multipliers
- 16Tailwind Cursor Spotlight Card with Masked Border Highlight
- 17Tailwind 3D Tilt Product Card with Specular Glare
- 18Tailwind Slide-Up Panel Card with Peek Header Swap
- 19Tailwind Credit Card Flip with Magnetic Strip & CVV
- 20Tailwind RGB Channel-Split Glitch Card
- 21Tailwind Rotating Conic Aura Card with Blurred Gradient Bloom
- 22Tailwind Blob-to-Rectangle Border Morph Card
- 23Tailwind Magnetic Float Orb Card with Pointer Attraction
- 24Tailwind Typewriter Reveal Card with Steps Animation & Caret
- 25Tailwind Magazine Overlay Slide Card (Full-Face Panel Takeover)
- 26Tailwind Breathing Film Grain Card with SVG Turbulence Noise
- 27Tailwind Frosted Glass Intensify Card with Deepening Backdrop Blur
- 28Tailwind Blueprint Reveal Card with Circular clip-path Wipe
- 29Tailwind Cursor-Anchored Shockwave Ripple Card
- 30Tailwind Color-Burn Ignition Card with Grayscale-to-Fire Hover
- 31Tailwind Prismatic Light Sweep Card with Screen Blend Bar
- 32Tailwind Book Cover Open Card with Hinged 3D Rotation
- 33Tailwind Drifting Aurora Ribbons Card with Blurred Gradient Bands
Tailwind 3D Tilt & Parallax Card with Depth Layers
A featured-work card that rotates toward the cursor in real 3D while background, badge, title and copy ride separate Z planes and drift at different rates. The port's interesting cost: perspective, transform-style and translateZ have no utilities and stay arbitrary properties — but the whole child-layer contract collapses into one *: variant on the card.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tch-01 w-full min-h-svh grid place-items-center bg-tch01-bg bg-[radial-gradient(circle_at_30%_20%,var(--color-tch01-glow),var(--color-tch01-bg)_65%)] font-tch01-sans p-8 [perspective:1300px]">
<article data-tch01="root" tabindex="0" class="group relative w-[340px] h-[460px] rounded-[26px] bg-tch01-card border border-white/10 shadow-[0_30px_60px_rgb(0_0_0/0.5)] cursor-pointer [transform-style:preserve-3d] transition-transform duration-150 ease-out outline-none focus-visible:outline-3 focus-visible:outline-tch01-acc focus-visible:outline-offset-4 *:absolute *:transition-transform *:duration-150 *:ease-out *:will-change-transform motion-reduce:transition-none motion-reduce:*:transition-none">
<div data-depth="10" class="inset-0 rounded-[26px] overflow-hidden bg-[radial-gradient(circle_at_30%_25%,#ff7a59_0_18%,transparent_19%),radial-gradient(circle_at_75%_70%,#5b8cff_0_24%,transparent_25%),linear-gradient(160deg,#2a2f63,#11132c)] after:content-[''] after:absolute after:inset-0 after:bg-[radial-gradient(circle_at_50%_40%,transparent,rgb(9_10_24/0.6))]"></div>
<span data-depth="34" class="top-8 left-8 z-5 text-[0.7rem] tracking-[0.22em] uppercase text-tch01-tag">Featured Work</span>
<div data-depth="46" class="top-8 right-8 z-5 grid place-items-center size-[46px] rounded-[14px] bg-white/12 border border-white/25 text-white font-bold backdrop-blur-[4px]">◈</div>
<h2 data-depth="60" class="left-8 bottom-[5.4rem] z-5 font-tch01-display font-black text-[3rem] leading-[0.9] text-white">Aether<br>Studio</h2>
<p data-depth="40" class="left-8 right-8 bottom-8 z-5 text-[0.85rem] leading-[1.55] text-tch01-body">An award-winning interactive site. Background, type, and badge each ride their own depth plane.</p>
<div data-tch01="glare" class="inset-0 z-6 rounded-[26px] pointer-events-none opacity-0 transition-opacity duration-300 group-hover:opacity-100 bg-[radial-gradient(circle_at_var(--tch01-gx,50%)_var(--tch01-gy,50%),rgb(255_255_255/0.22),transparent_45%)]"></div>
</article>
</section><section class="tch-01 w-full min-h-svh grid place-items-center bg-tch01-bg bg-[radial-gradient(circle_at_30%_20%,var(--color-tch01-glow),var(--color-tch01-bg)_65%)] font-tch01-sans p-8 [perspective:1300px]">
<article data-tch01="root" tabindex="0" class="group relative w-[340px] h-[460px] rounded-[26px] bg-tch01-card border border-white/10 shadow-[0_30px_60px_rgb(0_0_0/0.5)] cursor-pointer [transform-style:preserve-3d] transition-transform duration-150 ease-out outline-none focus-visible:outline-3 focus-visible:outline-tch01-acc focus-visible:outline-offset-4 *:absolute *:transition-transform *:duration-150 *:ease-out *:will-change-transform motion-reduce:transition-none motion-reduce:*:transition-none">
<div data-depth="10" class="inset-0 rounded-[26px] overflow-hidden bg-[radial-gradient(circle_at_30%_25%,#ff7a59_0_18%,transparent_19%),radial-gradient(circle_at_75%_70%,#5b8cff_0_24%,transparent_25%),linear-gradient(160deg,#2a2f63,#11132c)] after:content-[''] after:absolute after:inset-0 after:bg-[radial-gradient(circle_at_50%_40%,transparent,rgb(9_10_24/0.6))]"></div>
<span data-depth="34" class="top-8 left-8 z-5 text-[0.7rem] tracking-[0.22em] uppercase text-tch01-tag">Featured Work</span>
<div data-depth="46" class="top-8 right-8 z-5 grid place-items-center size-[46px] rounded-[14px] bg-white/12 border border-white/25 text-white font-bold backdrop-blur-[4px]">◈</div>
<h2 data-depth="60" class="left-8 bottom-[5.4rem] z-5 font-tch01-display font-black text-[3rem] leading-[0.9] text-white">Aether<br>Studio</h2>
<p data-depth="40" class="left-8 right-8 bottom-8 z-5 text-[0.85rem] leading-[1.55] text-tch01-body">An award-winning interactive site. Background, type, and badge each ride their own depth plane.</p>
<div data-tch01="glare" class="inset-0 z-6 rounded-[26px] pointer-events-none opacity-0 transition-opacity duration-300 group-hover:opacity-100 bg-[radial-gradient(circle_at_var(--tch01-gx,50%)_var(--tch01-gy,50%),rgb(255_255_255/0.22),transparent_45%)]"></div>
</article>
</section>@import "tailwindcss";
@theme {
--color-tch01-bg: #090a18;
--color-tch01-bg: oklch(0.13 0.03 275);
--color-tch01-glow: #1f2347;
--color-tch01-glow: oklch(0.26 0.06 275);
--color-tch01-card: #0e1024;
--color-tch01-card: oklch(0.16 0.04 275);
--color-tch01-tag: #ffd6c4;
--color-tch01-tag: oklch(0.89 0.05 45);
--color-tch01-body: #c4c8e6;
--color-tch01-body: oklch(0.82 0.04 280);
--color-tch01-acc: #ff7a59;
--color-tch01-acc: oklch(0.72 0.17 40);
--font-tch01-sans: 'Inter Tight', system-ui, sans-serif;
--font-tch01-display: 'Fraunces', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build compiles. */
.tch-01 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tch01-bg: #090a18;
--color-tch01-bg: oklch(0.13 0.03 275);
--color-tch01-glow: #1f2347;
--color-tch01-glow: oklch(0.26 0.06 275);
--color-tch01-card: #0e1024;
--color-tch01-card: oklch(0.16 0.04 275);
--color-tch01-tag: #ffd6c4;
--color-tch01-tag: oklch(0.89 0.05 45);
--color-tch01-body: #c4c8e6;
--color-tch01-body: oklch(0.82 0.04 280);
--color-tch01-acc: #ff7a59;
--color-tch01-acc: oklch(0.72 0.17 40);
--font-tch01-sans: 'Inter Tight', system-ui, sans-serif;
--font-tch01-display: 'Fraunces', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build compiles. */
.tch-01 {
width: 100%;
min-height: 100svh;
}(() => {
const root = document.querySelector('.tch-01');
if (!root) return;
const card = root.querySelector('[data-tch01="root"]');
if (!card) return;
if (!matchMedia('(hover: hover) and (pointer: fine)').matches) return;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const layers = card.querySelectorAll('[data-depth]');
let frame = 0;
card.addEventListener('mousemove', (e) => {
if (frame) return;
frame = requestAnimationFrame(() => {
frame = 0;
const r = card.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width;
const py = (e.clientY - r.top) / r.height;
card.style.transform = 'rotateY(' + ((px - 0.5) * 18) + 'deg) rotateX(' + ((0.5 - py) * 18) + 'deg)';
layers.forEach((l) => {
const d = parseFloat(l.dataset.depth);
l.style.transform = 'translateZ(' + (d) + 'px) translate(' + ((0.5 - px) * d * 0.6) + 'px, ' + ((0.5 - py) * d * 0.6) + 'px)';
});
card.style.setProperty('--tch01-gx', '' + (px * 100) + '%');
card.style.setProperty('--tch01-gy', '' + (py * 100) + '%');
});
});
card.addEventListener('mouseleave', () => {
card.style.transform = 'rotateY(0deg) rotateX(0deg)';
layers.forEach((l) => {
l.style.transform = 'translateZ(' + (parseFloat(l.dataset.depth)) + 'px)';
});
});
})();(() => {
const root = document.querySelector('.tch-01');
if (!root) return;
const card = root.querySelector('[data-tch01="root"]');
if (!card) return;
if (!matchMedia('(hover: hover) and (pointer: fine)').matches) return;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const layers = card.querySelectorAll('[data-depth]');
let frame = 0;
card.addEventListener('mousemove', (e) => {
if (frame) return;
frame = requestAnimationFrame(() => {
frame = 0;
const r = card.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width;
const py = (e.clientY - r.top) / r.height;
card.style.transform = 'rotateY(' + ((px - 0.5) * 18) + 'deg) rotateX(' + ((0.5 - py) * 18) + 'deg)';
layers.forEach((l) => {
const d = parseFloat(l.dataset.depth);
l.style.transform = 'translateZ(' + (d) + 'px) translate(' + ((0.5 - px) * d * 0.6) + 'px, ' + ((0.5 - py) * d * 0.6) + 'px)';
});
card.style.setProperty('--tch01-gx', '' + (px * 100) + '%');
card.style.setProperty('--tch01-gy', '' + (py * 100) + '%');
});
});
card.addEventListener('mouseleave', () => {
card.style.transform = 'rotateY(0deg) rotateX(0deg)';
layers.forEach((l) => {
l.style.transform = 'translateZ(' + (parseFloat(l.dataset.depth)) + 'px)';
});
});
})();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-tch01-bg: #090a18;
--color-tch01-bg: oklch(0.13 0.03 275);
--color-tch01-glow: #1f2347;
--color-tch01-glow: oklch(0.26 0.06 275);
--color-tch01-card: #0e1024;
--color-tch01-card: oklch(0.16 0.04 275);
--color-tch01-tag: #ffd6c4;
--color-tch01-tag: oklch(0.89 0.05 45);
--color-tch01-body: #c4c8e6;
--color-tch01-body: oklch(0.82 0.04 280);
--color-tch01-acc: #ff7a59;
--color-tch01-acc: oklch(0.72 0.17 40);
--font-tch01-sans: 'Inter Tight', system-ui, sans-serif;
--font-tch01-display: 'Fraunces', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build compiles. */
.tch-01 {
width: 100%;
min-height: 100svh;
}@import "tailwindcss";
@theme {
--color-tch01-bg: #090a18;
--color-tch01-bg: oklch(0.13 0.03 275);
--color-tch01-glow: #1f2347;
--color-tch01-glow: oklch(0.26 0.06 275);
--color-tch01-card: #0e1024;
--color-tch01-card: oklch(0.16 0.04 275);
--color-tch01-tag: #ffd6c4;
--color-tch01-tag: oklch(0.89 0.05 45);
--color-tch01-body: #c4c8e6;
--color-tch01-body: oklch(0.82 0.04 280);
--color-tch01-acc: #ff7a59;
--color-tch01-acc: oklch(0.72 0.17 40);
--font-tch01-sans: 'Inter Tight', system-ui, sans-serif;
--font-tch01-display: 'Fraunces', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build compiles. */
.tch-01 {
width: 100%;
min-height: 100svh;
}<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=Fraunces:opsz,wght@9..144,500;9..144,900&family=Inter+Tight: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=Fraunces:opsz,wght@9..144,500;9..144,900&family=Inter+Tight:wght@400;500;600&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
.card-01__card > *becomes*:absolute *:transition-transform *:duration-150. One rule, one class list, and the layer contract is now readable on the element that owns it.perspective,transform-styleandtranslateZstayed arbitrary properties on purpose — they are scene geometry, not design tokens, so minting@themeentries for them would buy nothing.- UI change from the CSS original: the card is now
tabindex="0"with a realfocus-visiblering, and the JS refuses to attach on coarse pointers or under reduced motion instead of jittering.
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 Tilt & Parallax Card with Depth Layers
Source: https://codefronts.com/motion/tailwind-css-card-hover-effects/tailwind-3d-tilt-parallax-card-hover-effect/
A featured-work card that rotates toward the cursor in real 3D while background, badge, title and copy ride separate Z planes and drift at different rates. The port's interesting cost: perspective, transform-style and translateZ have no utilities and stay arbitrary properties — but the whole child-layer contract collapses into one *: variant on the card.
## HTML
```html
<section class="tch-01 w-full min-h-svh grid place-items-center bg-tch01-bg bg-[radial-gradient(circle_at_30%_20%,var(--color-tch01-glow),var(--color-tch01-bg)_65%)] font-tch01-sans p-8 [perspective:1300px]">
<article data-tch01="root" tabindex="0" class="group relative w-[340px] h-[460px] rounded-[26px] bg-tch01-card border border-white/10 shadow-[0_30px_60px_rgb(0_0_0/0.5)] cursor-pointer [transform-style:preserve-3d] transition-transform duration-150 ease-out outline-none focus-visible:outline-3 focus-visible:outline-tch01-acc focus-visible:outline-offset-4 *:absolute *:transition-transform *:duration-150 *:ease-out *:will-change-transform motion-reduce:transition-none motion-reduce:*:transition-none">
<div data-depth="10" class="inset-0 rounded-[26px] overflow-hidden bg-[radial-gradient(circle_at_30%_25%,#ff7a59_0_18%,transparent_19%),radial-gradient(circle_at_75%_70%,#5b8cff_0_24%,transparent_25%),linear-gradient(160deg,#2a2f63,#11132c)] after:content-[''] after:absolute after:inset-0 after:bg-[radial-gradient(circle_at_50%_40%,transparent,rgb(9_10_24/0.6))]"></div>
<span data-depth="34" class="top-8 left-8 z-5 text-[0.7rem] tracking-[0.22em] uppercase text-tch01-tag">Featured Work</span>
<div data-depth="46" class="top-8 right-8 z-5 grid place-items-center size-[46px] rounded-[14px] bg-white/12 border border-white/25 text-white font-bold backdrop-blur-[4px]">◈</div>
<h2 data-depth="60" class="left-8 bottom-[5.4rem] z-5 font-tch01-display font-black text-[3rem] leading-[0.9] text-white">Aether<br>Studio</h2>
<p data-depth="40" class="left-8 right-8 bottom-8 z-5 text-[0.85rem] leading-[1.55] text-tch01-body">An award-winning interactive site. Background, type, and badge each ride their own depth plane.</p>
<div data-tch01="glare" class="inset-0 z-6 rounded-[26px] pointer-events-none opacity-0 transition-opacity duration-300 group-hover:opacity-100 bg-[radial-gradient(circle_at_var(--tch01-gx,50%)_var(--tch01-gy,50%),rgb(255_255_255/0.22),transparent_45%)]"></div>
</article>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tch01-bg: #090a18;
--color-tch01-bg: oklch(0.13 0.03 275);
--color-tch01-glow: #1f2347;
--color-tch01-glow: oklch(0.26 0.06 275);
--color-tch01-card: #0e1024;
--color-tch01-card: oklch(0.16 0.04 275);
--color-tch01-tag: #ffd6c4;
--color-tch01-tag: oklch(0.89 0.05 45);
--color-tch01-body: #c4c8e6;
--color-tch01-body: oklch(0.82 0.04 280);
--color-tch01-acc: #ff7a59;
--color-tch01-acc: oklch(0.72 0.17 40);
--font-tch01-sans: 'Inter Tight', system-ui, sans-serif;
--font-tch01-display: 'Fraunces', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build compiles. */
.tch-01 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.tch-01');
if (!root) return;
const card = root.querySelector('[data-tch01="root"]');
if (!card) return;
if (!matchMedia('(hover: hover) and (pointer: fine)').matches) return;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const layers = card.querySelectorAll('[data-depth]');
let frame = 0;
card.addEventListener('mousemove', (e) => {
if (frame) return;
frame = requestAnimationFrame(() => {
frame = 0;
const r = card.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width;
const py = (e.clientY - r.top) / r.height;
card.style.transform = 'rotateY(' + ((px - 0.5) * 18) + 'deg) rotateX(' + ((0.5 - py) * 18) + 'deg)';
layers.forEach((l) => {
const d = parseFloat(l.dataset.depth);
l.style.transform = 'translateZ(' + (d) + 'px) translate(' + ((0.5 - px) * d * 0.6) + 'px, ' + ((0.5 - py) * d * 0.6) + 'px)';
});
card.style.setProperty('--tch01-gx', '' + (px * 100) + '%');
card.style.setProperty('--tch01-gy', '' + (py * 100) + '%');
});
});
card.addEventListener('mouseleave', () => {
card.style.transform = 'rotateY(0deg) rotateX(0deg)';
layers.forEach((l) => {
l.style.transform = 'translateZ(' + (parseFloat(l.dataset.depth)) + 'px)';
});
});
})();
```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 Tilt & Parallax Card with Depth Layers
Source: https://codefronts.com/motion/tailwind-css-card-hover-effects/tailwind-3d-tilt-parallax-card-hover-effect/
A featured-work card that rotates toward the cursor in real 3D while background, badge, title and copy ride separate Z planes and drift at different rates. The port's interesting cost: perspective, transform-style and translateZ have no utilities and stay arbitrary properties — but the whole child-layer contract collapses into one *: variant on the card.
## HTML
```html
<section class="tch-01 w-full min-h-svh grid place-items-center bg-tch01-bg bg-[radial-gradient(circle_at_30%_20%,var(--color-tch01-glow),var(--color-tch01-bg)_65%)] font-tch01-sans p-8 [perspective:1300px]">
<article data-tch01="root" tabindex="0" class="group relative w-[340px] h-[460px] rounded-[26px] bg-tch01-card border border-white/10 shadow-[0_30px_60px_rgb(0_0_0/0.5)] cursor-pointer [transform-style:preserve-3d] transition-transform duration-150 ease-out outline-none focus-visible:outline-3 focus-visible:outline-tch01-acc focus-visible:outline-offset-4 *:absolute *:transition-transform *:duration-150 *:ease-out *:will-change-transform motion-reduce:transition-none motion-reduce:*:transition-none">
<div data-depth="10" class="inset-0 rounded-[26px] overflow-hidden bg-[radial-gradient(circle_at_30%_25%,#ff7a59_0_18%,transparent_19%),radial-gradient(circle_at_75%_70%,#5b8cff_0_24%,transparent_25%),linear-gradient(160deg,#2a2f63,#11132c)] after:content-[''] after:absolute after:inset-0 after:bg-[radial-gradient(circle_at_50%_40%,transparent,rgb(9_10_24/0.6))]"></div>
<span data-depth="34" class="top-8 left-8 z-5 text-[0.7rem] tracking-[0.22em] uppercase text-tch01-tag">Featured Work</span>
<div data-depth="46" class="top-8 right-8 z-5 grid place-items-center size-[46px] rounded-[14px] bg-white/12 border border-white/25 text-white font-bold backdrop-blur-[4px]">◈</div>
<h2 data-depth="60" class="left-8 bottom-[5.4rem] z-5 font-tch01-display font-black text-[3rem] leading-[0.9] text-white">Aether<br>Studio</h2>
<p data-depth="40" class="left-8 right-8 bottom-8 z-5 text-[0.85rem] leading-[1.55] text-tch01-body">An award-winning interactive site. Background, type, and badge each ride their own depth plane.</p>
<div data-tch01="glare" class="inset-0 z-6 rounded-[26px] pointer-events-none opacity-0 transition-opacity duration-300 group-hover:opacity-100 bg-[radial-gradient(circle_at_var(--tch01-gx,50%)_var(--tch01-gy,50%),rgb(255_255_255/0.22),transparent_45%)]"></div>
</article>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tch01-bg: #090a18;
--color-tch01-bg: oklch(0.13 0.03 275);
--color-tch01-glow: #1f2347;
--color-tch01-glow: oklch(0.26 0.06 275);
--color-tch01-card: #0e1024;
--color-tch01-card: oklch(0.16 0.04 275);
--color-tch01-tag: #ffd6c4;
--color-tch01-tag: oklch(0.89 0.05 45);
--color-tch01-body: #c4c8e6;
--color-tch01-body: oklch(0.82 0.04 280);
--color-tch01-acc: #ff7a59;
--color-tch01-acc: oklch(0.72 0.17 40);
--font-tch01-sans: 'Inter Tight', system-ui, sans-serif;
--font-tch01-display: 'Fraunces', Georgia, serif;
}
/* Root rule: the full-bleed contract, stated in CSS as well as in the markup's
w-full min-h-svh, so the stage is sized before the browser build compiles. */
.tch-01 {
width: 100%;
min-height: 100svh;
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.tch-01');
if (!root) return;
const card = root.querySelector('[data-tch01="root"]');
if (!card) return;
if (!matchMedia('(hover: hover) and (pointer: fine)').matches) return;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
const layers = card.querySelectorAll('[data-depth]');
let frame = 0;
card.addEventListener('mousemove', (e) => {
if (frame) return;
frame = requestAnimationFrame(() => {
frame = 0;
const r = card.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width;
const py = (e.clientY - r.top) / r.height;
card.style.transform = 'rotateY(' + ((px - 0.5) * 18) + 'deg) rotateX(' + ((0.5 - py) * 18) + 'deg)';
layers.forEach((l) => {
const d = parseFloat(l.dataset.depth);
l.style.transform = 'translateZ(' + (d) + 'px) translate(' + ((0.5 - px) * d * 0.6) + 'px, ' + ((0.5 - py) * d * 0.6) + 'px)';
});
card.style.setProperty('--tch01-gx', '' + (px * 100) + '%');
card.style.setProperty('--tch01-gy', '' + (py * 100) + '%');
});
});
card.addEventListener('mouseleave', () => {
card.style.transform = 'rotateY(0deg) rotateX(0deg)';
layers.forEach((l) => {
l.style.transform = 'translateZ(' + (parseFloat(l.dataset.depth)) + 'px)';
});
});
})();
```How this works
Perspective stays on the parent. The stage carries [perspective:1300px]; the card carries [transform-style:preserve-3d]. Split like that, a rotateY on the card reads as depth and its children can sit on their own Z planes. Merge them onto one element and the 3D collapses into a flat skew — the single most common bug in this pattern, unchanged by the port.
The child rule becomes a variant. Every layer needs position:absolute and the same 150ms transform transition. In CSS that is a descendant rule; here it is *:absolute *:transition-transform *:duration-150 *:ease-out *:will-change-transform on the card, so the contract is visible on the element that enforces it.
JS writes properties, CSS owns the look. The handler normalises the pointer against getBoundingClientRect(), sets the card's transform, pushes each [data-depth] layer along Z, and writes the two glare properties. No classes are toggled, so the utilities remain the single source of styling.
Make it yours
- Tilt amount lives in the JS multiplier (
18); drop to8for subtle, raise to26for arcade. - Depth is per-element: change any layer's
data-depthand both its Z offset and its drift rate follow. - Retheme from
--color-tch01-bg,--color-tch01-cardand--color-tch01-acc— the stage gradient, card face and focus ring all read them.
Gotchas — read before shipping
[perspective:…]belongs on the stage, never on the tilting card.- Guard the listener with
(hover: hover) and (pointer: fine); on touch, syntheticmousemoveevents make the card jitter. *:applies to every direct child, glare included — which is why the glare's own opacity transition is declared separately rather than inherited from the variant.- Arbitrary
z-5/z-6bare values need v4; in v3 they would have to be arbitrary[z-index:5].
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
3D transforms and preserve-3d are universally supported; the quoted floor is Tailwind v4's own generated sheet (cascade layers, @property, color-mix()).