Pure TailwindMIT licensed

Tailwind Grid Image Blur Overlay Text Hover

Blur the image through the overlay rather than blurring the image: group-hover:backdrop-blur-lg plus group-hover:backdrop-brightness-60, which is the port's cleanest single win — Tailwind ships composable backdrop-filter utilities, so a two-function filter stack that was one hand-written declaration is now two named utilities that read like the intent.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="tih-04 w-full min-h-svh grid place-items-center bg-tih04-bg font-tih04-sans px-6 py-10" aria-label="Backdrop blur overlay text hover demo">
  <div class="grid w-full max-w-195 gap-4 grid-cols-1 sm:grid-cols-3">
    <article class="group relative overflow-hidden rounded-[14px] aspect-[3/4]">
      <div class="absolute inset-0 bg-[image:url('https://picsum.photos/seed/tih04-canvas/600/800'),linear-gradient(135deg,oklch(0.17_0.07_285),oklch(0.34_0.12_285),oklch(0.28_0.06_290))] bg-cover bg-center bg-no-repeat transition-transform duration-400 ease-tih04 group-hover:scale-105 motion-reduce:transition-none motion-reduce:group-hover:scale-100"></div>
      <div class="absolute inset-0 flex flex-col justify-end p-5 bg-transparent backdrop-blur-none backdrop-brightness-100 transition-[backdrop-filter,background-color] duration-400 ease-tih04 group-hover:bg-[oklch(0_0_0/0.15)] group-hover:backdrop-blur-lg group-hover:backdrop-brightness-60 motion-reduce:transition-none motion-reduce:bg-[oklch(0_0_0/0.15)] motion-reduce:backdrop-blur-lg motion-reduce:backdrop-brightness-60">
        <p style="--i:0" class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih04-acc opacity-0 translate-y-2.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Editorial</p>
        <h3 style="--i:1" class="text-[15px] font-bold leading-[1.3] text-tih04-text opacity-0 translate-y-3 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0"><a href="#infinite-canvas" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih04-acc focus-visible:outline-offset-3">The Infinite Canvas</a></h3>
        <p style="--i:2" class="mt-1.25 text-[11px] text-tih04-muted opacity-0 translate-y-3.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Exploring the boundaries of digital art in 2026</p>
      </div>
    </article>
    <article class="group relative overflow-hidden rounded-[14px] aspect-[3/4]">
      <div class="absolute inset-0 bg-[image:url('https://picsum.photos/seed/tih04-ocean/600/800'),linear-gradient(135deg,oklch(0.3_0.07_165),oklch(0.42_0.09_165),oklch(0.85_0.13_165))] bg-cover bg-center bg-no-repeat transition-transform duration-400 ease-tih04 group-hover:scale-105 motion-reduce:transition-none motion-reduce:group-hover:scale-100"></div>
      <div class="absolute inset-0 flex flex-col justify-end p-5 bg-transparent backdrop-blur-none backdrop-brightness-100 transition-[backdrop-filter,background-color] duration-400 ease-tih04 group-hover:bg-[oklch(0_0_0/0.15)] group-hover:backdrop-blur-lg group-hover:backdrop-brightness-60 motion-reduce:transition-none motion-reduce:bg-[oklch(0_0_0/0.15)] motion-reduce:backdrop-blur-lg motion-reduce:backdrop-brightness-60">
        <p style="--i:0" class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih04-acc opacity-0 translate-y-2.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Nature</p>
        <h3 style="--i:1" class="text-[15px] font-bold leading-[1.3] text-tih04-text opacity-0 translate-y-3 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0"><a href="#bioluminescent" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih04-acc focus-visible:outline-offset-3">Bioluminescent Depths</a></h3>
        <p style="--i:2" class="mt-1.25 text-[11px] text-tih04-muted opacity-0 translate-y-3.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Life thriving in the ocean's twilight zone</p>
      </div>
    </article>
    <article class="group relative overflow-hidden rounded-[14px] aspect-[3/4]">
      <div class="absolute inset-0 bg-[image:url('https://picsum.photos/seed/tih04-neon/600/800'),linear-gradient(135deg,oklch(0.16_0.06_350),oklch(0.35_0.15_350),oklch(0.75_0.2_330))] bg-cover bg-center bg-no-repeat transition-transform duration-400 ease-tih04 group-hover:scale-105 motion-reduce:transition-none motion-reduce:group-hover:scale-100"></div>
      <div class="absolute inset-0 flex flex-col justify-end p-5 bg-transparent backdrop-blur-none backdrop-brightness-100 transition-[backdrop-filter,background-color] duration-400 ease-tih04 group-hover:bg-[oklch(0_0_0/0.15)] group-hover:backdrop-blur-lg group-hover:backdrop-brightness-60 motion-reduce:transition-none motion-reduce:bg-[oklch(0_0_0/0.15)] motion-reduce:backdrop-blur-lg motion-reduce:backdrop-brightness-60">
        <p style="--i:0" class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih04-acc opacity-0 translate-y-2.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Culture</p>
        <h3 style="--i:1" class="text-[15px] font-bold leading-[1.3] text-tih04-text opacity-0 translate-y-3 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0"><a href="#neon-flux" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih04-acc focus-visible:outline-offset-3">Neon Flux Collection</a></h3>
        <p style="--i:2" class="mt-1.25 text-[11px] text-tih04-muted opacity-0 translate-y-3.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Street art meets future-forward design aesthetics</p>
      </div>
    </article>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tih04-bg: #080810;
  --color-tih04-bg: oklch(0.12 0.012 285);
  --color-tih04-text: #f1f5f9;
  --color-tih04-text: oklch(0.96 0.005 250);
  --color-tih04-muted: #94a3b8;
  --color-tih04-muted: oklch(0.7 0.03 250);
  --color-tih04-acc: #8b5cf6;
  --color-tih04-acc: oklch(0.62 0.21 295);
  --font-tih04-sans: 'Space Grotesk', system-ui, sans-serif;
  --ease-tih04: 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-04 {
  width: 100%;
  min-height: 100svh;
}

.tih-04 ::selection {
  background: oklch(0.62 0.21 295);
  color: #fff;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tih04-bg: #080810;
  --color-tih04-bg: oklch(0.12 0.012 285);
  --color-tih04-text: #f1f5f9;
  --color-tih04-text: oklch(0.96 0.005 250);
  --color-tih04-muted: #94a3b8;
  --color-tih04-muted: oklch(0.7 0.03 250);
  --color-tih04-acc: #8b5cf6;
  --color-tih04-acc: oklch(0.62 0.21 295);
  --font-tih04-sans: 'Space Grotesk', system-ui, sans-serif;
  --ease-tih04: 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-04 {
  width: 100%;
  min-height: 100svh;
}

.tih-04 ::selection {
  background: oklch(0.62 0.21 295);
  color: #fff;
}
3Fonts used by this demooptional — skip if you have your own type stack
<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 clearest utility win in the collection: a two-function backdrop-filter stack plus its -webkit- twin becomes backdrop-blur-lg backdrop-brightness-60. Tailwind composes the value and emits the prefix.
  • transition-[backdrop-filter,background-color] is deliberately explicit. transition-all here also animates the caption's inherited properties, and the reveal smears.
  • The scrim stays a separate bg- utility rather than being folded into the filter, so browsers without backdrop-filter still get a legible caption.
  • The caption stagger reuses the --i pattern from demo 02 unchanged — one idea, reused, rather than a second set of multiplier rules.
  • motion-reduce: promotes the hovered state to the resting state: the frost and the caption are simply always there, which is the correct no-motion answer for a blur.
  • Only the root rule and ::selection stay in CSS. This demo's stylesheet is the shortest in the collection, and the CSS version's was one of the longest.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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 Grid Image Blur Overlay Text Hover
Source: https://codefronts.com/motion/tailwind-css-image-hover-effects/tailwind-grid-image-blur-overlay-text-hover/

Blur the image through the overlay rather than blurring the image: group-hover:backdrop-blur-lg plus group-hover:backdrop-brightness-60, which is the port's cleanest single win &mdash; Tailwind ships composable backdrop-filter utilities, so a two-function filter stack that was one hand-written declaration is now two named utilities that read like the intent.
## HTML
```html
<section class="tih-04 w-full min-h-svh grid place-items-center bg-tih04-bg font-tih04-sans px-6 py-10" aria-label="Backdrop blur overlay text hover demo">
  <div class="grid w-full max-w-195 gap-4 grid-cols-1 sm:grid-cols-3">
    <article class="group relative overflow-hidden rounded-[14px] aspect-[3/4]">
      <div class="absolute inset-0 bg-[image:url('https://picsum.photos/seed/tih04-canvas/600/800'),linear-gradient(135deg,oklch(0.17_0.07_285),oklch(0.34_0.12_285),oklch(0.28_0.06_290))] bg-cover bg-center bg-no-repeat transition-transform duration-400 ease-tih04 group-hover:scale-105 motion-reduce:transition-none motion-reduce:group-hover:scale-100"></div>
      <div class="absolute inset-0 flex flex-col justify-end p-5 bg-transparent backdrop-blur-none backdrop-brightness-100 transition-[backdrop-filter,background-color] duration-400 ease-tih04 group-hover:bg-[oklch(0_0_0/0.15)] group-hover:backdrop-blur-lg group-hover:backdrop-brightness-60 motion-reduce:transition-none motion-reduce:bg-[oklch(0_0_0/0.15)] motion-reduce:backdrop-blur-lg motion-reduce:backdrop-brightness-60">
        <p style="--i:0" class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih04-acc opacity-0 translate-y-2.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Editorial</p>
        <h3 style="--i:1" class="text-[15px] font-bold leading-[1.3] text-tih04-text opacity-0 translate-y-3 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0"><a href="#infinite-canvas" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih04-acc focus-visible:outline-offset-3">The Infinite Canvas</a></h3>
        <p style="--i:2" class="mt-1.25 text-[11px] text-tih04-muted opacity-0 translate-y-3.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Exploring the boundaries of digital art in 2026</p>
      </div>
    </article>
    <article class="group relative overflow-hidden rounded-[14px] aspect-[3/4]">
      <div class="absolute inset-0 bg-[image:url('https://picsum.photos/seed/tih04-ocean/600/800'),linear-gradient(135deg,oklch(0.3_0.07_165),oklch(0.42_0.09_165),oklch(0.85_0.13_165))] bg-cover bg-center bg-no-repeat transition-transform duration-400 ease-tih04 group-hover:scale-105 motion-reduce:transition-none motion-reduce:group-hover:scale-100"></div>
      <div class="absolute inset-0 flex flex-col justify-end p-5 bg-transparent backdrop-blur-none backdrop-brightness-100 transition-[backdrop-filter,background-color] duration-400 ease-tih04 group-hover:bg-[oklch(0_0_0/0.15)] group-hover:backdrop-blur-lg group-hover:backdrop-brightness-60 motion-reduce:transition-none motion-reduce:bg-[oklch(0_0_0/0.15)] motion-reduce:backdrop-blur-lg motion-reduce:backdrop-brightness-60">
        <p style="--i:0" class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih04-acc opacity-0 translate-y-2.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Nature</p>
        <h3 style="--i:1" class="text-[15px] font-bold leading-[1.3] text-tih04-text opacity-0 translate-y-3 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0"><a href="#bioluminescent" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih04-acc focus-visible:outline-offset-3">Bioluminescent Depths</a></h3>
        <p style="--i:2" class="mt-1.25 text-[11px] text-tih04-muted opacity-0 translate-y-3.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Life thriving in the ocean's twilight zone</p>
      </div>
    </article>
    <article class="group relative overflow-hidden rounded-[14px] aspect-[3/4]">
      <div class="absolute inset-0 bg-[image:url('https://picsum.photos/seed/tih04-neon/600/800'),linear-gradient(135deg,oklch(0.16_0.06_350),oklch(0.35_0.15_350),oklch(0.75_0.2_330))] bg-cover bg-center bg-no-repeat transition-transform duration-400 ease-tih04 group-hover:scale-105 motion-reduce:transition-none motion-reduce:group-hover:scale-100"></div>
      <div class="absolute inset-0 flex flex-col justify-end p-5 bg-transparent backdrop-blur-none backdrop-brightness-100 transition-[backdrop-filter,background-color] duration-400 ease-tih04 group-hover:bg-[oklch(0_0_0/0.15)] group-hover:backdrop-blur-lg group-hover:backdrop-brightness-60 motion-reduce:transition-none motion-reduce:bg-[oklch(0_0_0/0.15)] motion-reduce:backdrop-blur-lg motion-reduce:backdrop-brightness-60">
        <p style="--i:0" class="mb-1.5 text-[10px] font-bold tracking-[0.12em] uppercase text-tih04-acc opacity-0 translate-y-2.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Culture</p>
        <h3 style="--i:1" class="text-[15px] font-bold leading-[1.3] text-tih04-text opacity-0 translate-y-3 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0"><a href="#neon-flux" class="no-underline text-inherit rounded focus-visible:outline-2 focus-visible:outline-tih04-acc focus-visible:outline-offset-3">Neon Flux Collection</a></h3>
        <p style="--i:2" class="mt-1.25 text-[11px] text-tih04-muted opacity-0 translate-y-3.5 transition-[opacity,translate] duration-400 ease-tih04 group-hover:opacity-100 group-hover:translate-y-0 group-hover:[transition-delay:calc(var(--i)*60ms)] motion-reduce:transition-none motion-reduce:opacity-100 motion-reduce:translate-y-0">Street art meets future-forward design aesthetics</p>
      </div>
    </article>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tih04-bg: #080810;
  --color-tih04-bg: oklch(0.12 0.012 285);
  --color-tih04-text: #f1f5f9;
  --color-tih04-text: oklch(0.96 0.005 250);
  --color-tih04-muted: #94a3b8;
  --color-tih04-muted: oklch(0.7 0.03 250);
  --color-tih04-acc: #8b5cf6;
  --color-tih04-acc: oklch(0.62 0.21 295);
  --font-tih04-sans: 'Space Grotesk', system-ui, sans-serif;
  --ease-tih04: 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-04 {
  width: 100%;
  min-height: 100svh;
}

.tih-04 ::selection {
  background: oklch(0.62 0.21 295);
  color: #fff;
}
```

How this works

Composable filters are real utilities. The CSS version writes two declarations plus the -webkit- twin and transitions the shorthand. The port writes what it means:

<div class="absolute inset-0 backdrop-blur-none backdrop-brightness-100
   transition-[backdrop-filter,background-color] duration-400
   group-hover:backdrop-blur-lg group-hover:backdrop-brightness-60">

Tailwind composes the stack into one backdrop-filter value, so adding group-hover:backdrop-saturate-50 is one more utility rather than a rewritten declaration.

What the glass is over matters. backdrop-filter blurs whatever is painted behind the element, so the overlay has to sit above the artwork in the same stacking context — here, a later sibling inside the relative card. A sibling beside the image blurs nothing.

The caption rides on top of the glass. The three lines are children of the overlay, so they are never blurred by it — a filter applies to the backdrop, not to descendants. They only need the stagger.

Make it yours

  • Frost strength: backdrop-blur-md through backdrop-blur-2xl. The named scale is genuinely the right granularity here.
  • Contrast floor: group-hover:backdrop-brightness-45 for bright photography, where 60% is not enough for white text.
  • Brand wash: replace the black scrim with group-hover:bg-[oklch(0.55_0.24_295/0.15)] for a violet tint that still shows the image.
  • Add group-hover:backdrop-saturate-50 to desaturate as it blurs — one utility, no new declaration.
  • Retheme through --color-tih04-acc, which paints the eyebrow and every focus ring.

Gotchas — read before shipping

  • backdrop-filter blurs what is behind the element in paint order. Put the overlay before the artwork in the DOM and the effect silently does nothing.
  • Do not use transition-all on the overlay: it animates the children's inherited properties too and the caption arrives smeared.
  • Many blurred cards on screen at once is real GPU memory. Gate with an IntersectionObserver in production galleries.
  • Safari needs -webkit-backdrop-filter; Tailwind emits it, but only if you use the utilities rather than an arbitrary [backdrop-filter:…] property.
  • Firefox below 103 shows the caption with no blur. Because the scrim is a separate bg- utility, that fallback is still legible — which is why the scrim exists.
  • The browser CDN build compiles in the page and is for prototyping only. Ship the CLI or a plugin build.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

backdrop-filter is Chrome 76+, Safari 9+ prefixed and Firefox 103+; older Firefox degrades to the scrim-only overlay, which stays legible.

Techniques used in this demo

Theme tokensset in @theme, used by a utility

Arbitraryneeds bracket syntax

Search CodeFronts

Loading…