01 / 20
All 20 in 20 Tailwind CSS Image Hover Effects
- 01Tailwind Image Hover Zoom In Card
- 02Tailwind Hover Reveal Text Over Image
- 03Tailwind E-Commerce Product Image Swap on Hover
- 04Tailwind Grid Image Blur Overlay Text Hover
- 05Tailwind Card Slide Up Caption on Hover
- 06Tailwind Grayscale to Color Image Hover
- 07Tailwind Image Color Overlay Tint on Hover
- 08Tailwind Neumorphic Image Card Hover Effect
- 09Tailwind Glassmorphism Overlay on Image Hover
- 10Tailwind Glowing Neon Border Image Hover
- 11Tailwind Image Clip-Path Transition Hover
- 12Tailwind Image Tilt 3D Perspective Hover
- 13Tailwind Shining Light Glare Image Hover
- 14Tailwind Image Split Transition Hover
- 15Tailwind Image Ripple Animation Hover
- 16Tailwind Circular Team Profile Avatar Hover
- 17Tailwind Fullscreen Background Image Hover
- 18Tailwind Masonry Gallery Zoom Hover
- 19Tailwind Image Icon Reveal Center Hover
- 20Tailwind Image Card Flip 3D Animation Hover
Tailwind Image Hover Zoom In Card
The classic contained zoom, ported to one variant: overflow-hidden on the card and group-hover:scale-112 on the image, so the scale is clipped at the card boundary and the grid never shifts. The hover state lives on the element that moves — not in a .card:hover .img rule several screens away.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tih-01 w-full min-h-svh grid place-items-center bg-tih01-bg font-tih01-sans px-6 py-10" aria-label="Image hover zoom-in card demo">
<div class="grid w-full max-w-195 gap-5 grid-cols-1 sm:grid-cols-3">
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-observatory/800/600'),linear-gradient(135deg,oklch(0.19_0.11_300),oklch(0.35_0.2_295)_50%,oklch(0.6_0.12_220))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Explore</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#deep-space" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Deep Space Observatory</a></h3>
<p class="text-xs text-tih01-muted">12 min read · Cosmos</p>
</div>
</article>
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-forest/800/600'),linear-gradient(135deg,oklch(0.18_0.05_250),oklch(0.34_0.07_250)_50%,oklch(0.42_0.11_160))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Nature</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#forest-canopy" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Ancient Forest Canopy</a></h3>
<p class="text-xs text-tih01-muted">8 min read · Biology</p>
</div>
</article>
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-crystal/800/600'),linear-gradient(135deg,oklch(0.17_0.07_330),oklch(0.42_0.2_305)_50%,oklch(0.52_0.22_355))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Design</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#crystal" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Crystal Formations</a></h3>
<p class="text-xs text-tih01-muted">5 min read · Art</p>
</div>
</article>
</div>
</section><section class="tih-01 w-full min-h-svh grid place-items-center bg-tih01-bg font-tih01-sans px-6 py-10" aria-label="Image hover zoom-in card demo">
<div class="grid w-full max-w-195 gap-5 grid-cols-1 sm:grid-cols-3">
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-observatory/800/600'),linear-gradient(135deg,oklch(0.19_0.11_300),oklch(0.35_0.2_295)_50%,oklch(0.6_0.12_220))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Explore</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#deep-space" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Deep Space Observatory</a></h3>
<p class="text-xs text-tih01-muted">12 min read · Cosmos</p>
</div>
</article>
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-forest/800/600'),linear-gradient(135deg,oklch(0.18_0.05_250),oklch(0.34_0.07_250)_50%,oklch(0.42_0.11_160))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Nature</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#forest-canopy" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Ancient Forest Canopy</a></h3>
<p class="text-xs text-tih01-muted">8 min read · Biology</p>
</div>
</article>
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-crystal/800/600'),linear-gradient(135deg,oklch(0.17_0.07_330),oklch(0.42_0.2_305)_50%,oklch(0.52_0.22_355))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Design</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#crystal" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Crystal Formations</a></h3>
<p class="text-xs text-tih01-muted">5 min read · Art</p>
</div>
</article>
</div>
</section>@import "tailwindcss";
@theme {
--color-tih01-bg: #0f0f13;
--color-tih01-bg: oklch(0.16 0.008 285);
--color-tih01-card: #1a1a24;
--color-tih01-card: oklch(0.21 0.015 285);
--color-tih01-text: #e2e8f0;
--color-tih01-text: oklch(0.92 0.01 250);
--color-tih01-muted: #94a3b8;
--color-tih01-muted: oklch(0.7 0.03 250);
--color-tih01-acc: #7c3aed;
--color-tih01-acc: oklch(0.55 0.24 295);
--color-tih01-acc2: #06b6d4;
--color-tih01-acc2: oklch(0.71 0.14 205);
--font-tih01-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tih01: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
/* 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 correctly sized before the browser build compiles. */
.tih-01 {
width: 100%;
min-height: 100svh;
}
/* Scoped selection colour and the Safari rounded-corner-with-transform shim:
neither has a variant form. */
.tih-01 ::selection {
background: oklch(0.55 0.24 295);
color: #fff;
}
.tih-01 .group > div {
-webkit-mask-image: -webkit-radial-gradient(white, black);
}@import "tailwindcss";
@theme {
--color-tih01-bg: #0f0f13;
--color-tih01-bg: oklch(0.16 0.008 285);
--color-tih01-card: #1a1a24;
--color-tih01-card: oklch(0.21 0.015 285);
--color-tih01-text: #e2e8f0;
--color-tih01-text: oklch(0.92 0.01 250);
--color-tih01-muted: #94a3b8;
--color-tih01-muted: oklch(0.7 0.03 250);
--color-tih01-acc: #7c3aed;
--color-tih01-acc: oklch(0.55 0.24 295);
--color-tih01-acc2: #06b6d4;
--color-tih01-acc2: oklch(0.71 0.14 205);
--font-tih01-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tih01: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
/* 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 correctly sized before the browser build compiles. */
.tih-01 {
width: 100%;
min-height: 100svh;
}
/* Scoped selection colour and the Safari rounded-corner-with-transform shim:
neither has a variant form. */
.tih-01 ::selection {
background: oklch(0.55 0.24 295);
color: #fff;
}
.tih-01 .group > div {
-webkit-mask-image: -webkit-radial-gradient(white, black);
}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-tih01-bg: #0f0f13;
--color-tih01-bg: oklch(0.16 0.008 285);
--color-tih01-card: #1a1a24;
--color-tih01-card: oklch(0.21 0.015 285);
--color-tih01-text: #e2e8f0;
--color-tih01-text: oklch(0.92 0.01 250);
--color-tih01-muted: #94a3b8;
--color-tih01-muted: oklch(0.7 0.03 250);
--color-tih01-acc: #7c3aed;
--color-tih01-acc: oklch(0.55 0.24 295);
--color-tih01-acc2: #06b6d4;
--color-tih01-acc2: oklch(0.71 0.14 205);
--font-tih01-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tih01: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
/* 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 correctly sized before the browser build compiles. */
.tih-01 {
width: 100%;
min-height: 100svh;
}
/* Scoped selection colour and the Safari rounded-corner-with-transform shim:
neither has a variant form. */
.tih-01 ::selection {
background: oklch(0.55 0.24 295);
color: #fff;
}
.tih-01 .group > div {
-webkit-mask-image: -webkit-radial-gradient(white, black);
}@import "tailwindcss";
@theme {
--color-tih01-bg: #0f0f13;
--color-tih01-bg: oklch(0.16 0.008 285);
--color-tih01-card: #1a1a24;
--color-tih01-card: oklch(0.21 0.015 285);
--color-tih01-text: #e2e8f0;
--color-tih01-text: oklch(0.92 0.01 250);
--color-tih01-muted: #94a3b8;
--color-tih01-muted: oklch(0.7 0.03 250);
--color-tih01-acc: #7c3aed;
--color-tih01-acc: oklch(0.55 0.24 295);
--color-tih01-acc2: #06b6d4;
--color-tih01-acc2: oklch(0.71 0.14 205);
--font-tih01-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tih01: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
/* 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 correctly sized before the browser build compiles. */
.tih-01 {
width: 100%;
min-height: 100svh;
}
/* Scoped selection colour and the Safari rounded-corner-with-transform shim:
neither has a variant form. */
.tih-01 ::selection {
background: oklch(0.55 0.24 295);
color: #fff;
}
.tih-01 .group > div {
-webkit-mask-image: -webkit-radial-gradient(white, black);
}<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.
- The single
.card:hover .imgrule becomesgroupon the card andgroup-hover:scale-112on the image — the zoom is now readable from the element that performs it. - Three
__img--Nrules collapse into threebg-[image:url(…),linear-gradient(…)]utilities, one per card, so a card's photograph — and the gradient it falls back to — travels with its markup. - The
@media (prefers-reduced-motion)block becomesmotion-reduce:transition-noneplusmotion-reduce:group-hover:scale-100— the zoom is cancelled, not slowed. --easemoves from a plain custom property to an@themeentry, which mints theease-tih01utility; a plain:rootvariable would not.- The hover shadow stays an arbitrary value. A 60px violet glow is a one-off, not a step on a shadow scale, and
shadow-2xlwould be a lie. ::selectionand the-webkit-mask-imageSafari shim stay in CSS: no variant exists for either, and the shim is a bug workaround rather than a design decision.
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 Image Hover Zoom In Card
Source: https://codefronts.com/motion/tailwind-css-image-hover-effects/tailwind-image-hover-zoom-in-card/
The classic contained zoom, ported to one variant: overflow-hidden on the card and group-hover:scale-112 on the image, so the scale is clipped at the card boundary and the grid never shifts. The hover state lives on the element that moves — not in a .card:hover .img rule several screens away.
## HTML
```html
<section class="tih-01 w-full min-h-svh grid place-items-center bg-tih01-bg font-tih01-sans px-6 py-10" aria-label="Image hover zoom-in card demo">
<div class="grid w-full max-w-195 gap-5 grid-cols-1 sm:grid-cols-3">
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-observatory/800/600'),linear-gradient(135deg,oklch(0.19_0.11_300),oklch(0.35_0.2_295)_50%,oklch(0.6_0.12_220))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Explore</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#deep-space" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Deep Space Observatory</a></h3>
<p class="text-xs text-tih01-muted">12 min read · Cosmos</p>
</div>
</article>
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-forest/800/600'),linear-gradient(135deg,oklch(0.18_0.05_250),oklch(0.34_0.07_250)_50%,oklch(0.42_0.11_160))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Nature</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#forest-canopy" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Ancient Forest Canopy</a></h3>
<p class="text-xs text-tih01-muted">8 min read · Biology</p>
</div>
</article>
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-crystal/800/600'),linear-gradient(135deg,oklch(0.17_0.07_330),oklch(0.42_0.2_305)_50%,oklch(0.52_0.22_355))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Design</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#crystal" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Crystal Formations</a></h3>
<p class="text-xs text-tih01-muted">5 min read · Art</p>
</div>
</article>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tih01-bg: #0f0f13;
--color-tih01-bg: oklch(0.16 0.008 285);
--color-tih01-card: #1a1a24;
--color-tih01-card: oklch(0.21 0.015 285);
--color-tih01-text: #e2e8f0;
--color-tih01-text: oklch(0.92 0.01 250);
--color-tih01-muted: #94a3b8;
--color-tih01-muted: oklch(0.7 0.03 250);
--color-tih01-acc: #7c3aed;
--color-tih01-acc: oklch(0.55 0.24 295);
--color-tih01-acc2: #06b6d4;
--color-tih01-acc2: oklch(0.71 0.14 205);
--font-tih01-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tih01: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
/* 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 correctly sized before the browser build compiles. */
.tih-01 {
width: 100%;
min-height: 100svh;
}
/* Scoped selection colour and the Safari rounded-corner-with-transform shim:
neither has a variant form. */
.tih-01 ::selection {
background: oklch(0.55 0.24 295);
color: #fff;
}
.tih-01 .group > div {
-webkit-mask-image: -webkit-radial-gradient(white, black);
}
```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 Image Hover Zoom In Card
Source: https://codefronts.com/motion/tailwind-css-image-hover-effects/tailwind-image-hover-zoom-in-card/
The classic contained zoom, ported to one variant: overflow-hidden on the card and group-hover:scale-112 on the image, so the scale is clipped at the card boundary and the grid never shifts. The hover state lives on the element that moves — not in a .card:hover .img rule several screens away.
## HTML
```html
<section class="tih-01 w-full min-h-svh grid place-items-center bg-tih01-bg font-tih01-sans px-6 py-10" aria-label="Image hover zoom-in card demo">
<div class="grid w-full max-w-195 gap-5 grid-cols-1 sm:grid-cols-3">
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-observatory/800/600'),linear-gradient(135deg,oklch(0.19_0.11_300),oklch(0.35_0.2_295)_50%,oklch(0.6_0.12_220))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Explore</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#deep-space" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Deep Space Observatory</a></h3>
<p class="text-xs text-tih01-muted">12 min read · Cosmos</p>
</div>
</article>
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-forest/800/600'),linear-gradient(135deg,oklch(0.18_0.05_250),oklch(0.34_0.07_250)_50%,oklch(0.42_0.11_160))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Nature</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#forest-canopy" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Ancient Forest Canopy</a></h3>
<p class="text-xs text-tih01-muted">8 min read · Biology</p>
</div>
</article>
<article class="group overflow-hidden rounded-2xl bg-tih01-card border border-[oklch(1_0_0/0.06)] transition-[box-shadow,border-color] duration-450 ease-tih01 hover:shadow-[0_20px_60px_oklch(0.5_0.25_295/0.25)] hover:border-[oklch(0.55_0.22_295/0.35)] motion-reduce:transition-none">
<div class="relative overflow-hidden aspect-[4/3]">
<div class="grid place-items-center w-full h-full bg-[image:url('https://picsum.photos/seed/tih01-crystal/800/600'),linear-gradient(135deg,oklch(0.17_0.07_330),oklch(0.42_0.2_305)_50%,oklch(0.52_0.22_355))] bg-cover bg-center bg-no-repeat transition-transform duration-450 ease-tih01 group-hover:scale-112 motion-reduce:transition-none motion-reduce:group-hover:scale-100">
</div>
</div>
<div class="p-4">
<p class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih01-acc2">Design</p>
<h3 class="mb-1 text-sm font-semibold text-tih01-text"><a href="#crystal" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih01-acc focus-visible:outline-offset-3">Crystal Formations</a></h3>
<p class="text-xs text-tih01-muted">5 min read · Art</p>
</div>
</article>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tih01-bg: #0f0f13;
--color-tih01-bg: oklch(0.16 0.008 285);
--color-tih01-card: #1a1a24;
--color-tih01-card: oklch(0.21 0.015 285);
--color-tih01-text: #e2e8f0;
--color-tih01-text: oklch(0.92 0.01 250);
--color-tih01-muted: #94a3b8;
--color-tih01-muted: oklch(0.7 0.03 250);
--color-tih01-acc: #7c3aed;
--color-tih01-acc: oklch(0.55 0.24 295);
--color-tih01-acc2: #06b6d4;
--color-tih01-acc2: oklch(0.71 0.14 205);
--font-tih01-sans: 'Space Grotesk', system-ui, sans-serif;
--ease-tih01: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
/* 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 correctly sized before the browser build compiles. */
.tih-01 {
width: 100%;
min-height: 100svh;
}
/* Scoped selection colour and the Safari rounded-corner-with-transform shim:
neither has a variant form. */
.tih-01 ::selection {
background: oklch(0.55 0.24 295);
color: #fff;
}
.tih-01 .group > div {
-webkit-mask-image: -webkit-radial-gradient(white, black);
}
```How this works
The zoom is declared where it happens. Two elements, two class lists:
<article class="group overflow-hidden rounded-2xl">
<div class="overflow-hidden aspect-[4/3]">
<div class="transition-transform duration-450 ease-tih01
group-hover:scale-112">The card owns the clip and the shadow; the image owns the scale. Reading the image's class list tells you its resting size, its hover size, its curve and its reduced-motion answer without leaving the tag.
Why scale and not size. scale-112 compiles to scale: 112%, which runs on the compositor. A w-/h- animation would relayout the grid every frame; Tailwind can express both, and only one is correct.
The photo and its fallback are one utility. bg-[image:url(…),linear-gradient(…)] layers the photograph over the card's original gradient, so a slow or unreachable CDN shows the palette rather than a hole. The image: data-type hint is required — without it Tailwind reads the value as a colour.
Make it yours
- Zoom magnitude: the
112ingroup-hover:scale-112. Anything from105to120reads well; past that the crop starts to matter. - Tempo:
duration-450on the image and card.duration-250for dense product grids,duration-600for editorial. - Add a light-table feel with
group-hover:brightness-105on the image layer — one more utility, no new rule. - Swap the photograph by editing the
url()insidebg-[image:…], or use a real<img class="w-full h-full object-cover">instead —group-hover:scale-112moves to the<img>untouched. - Retheme through
--color-tih01-accand--color-tih01-acc2in@theme— the eyebrow text, the hover ring and the shadow all read them.
Gotchas — read before shipping
group-hover:needsgroupon the card. Put it on the grid instead and every card zooms whenever any card is hovered.- Keep
overflow-hiddenand the hover shadow on different elements — an ancestor clip also clips the glow. - Arbitrary shadow values cannot contain spaces:
shadow-[0_20px_60px_oklch(...)]needs the underscore escape throughout. - Hover is not a state keyboard users can reach. The card is an
<article>with a real link inside, and the link carries afocus-visible:ring — never rely ongroup-hover:alone for meaning. - Safari can briefly square off corners when
border-radiusmeetsoverflow-hiddenand a transform; the stylesheet keeps the-webkit-mask-imageshim for that, as no utility expresses it. - The browser CDN build compiles in the page and is for prototyping only. Ship the CLI or a plugin build.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
The zoom itself (transform, overflow, aspect-ratio) is Baseline and runs far back; the floor quoted is Tailwind v4's own generated sheet (cascade layers, @property, color-mix()).