04 / 22
All 22 in 22 Tailwind CSS Image Gallery
- 01Tailwind Hexagon Honeycomb Image Gallery with clip-path
- 02Tailwind Justified Image Gallery with Aspect-Ratio Flex Rows
- 03Tailwind Expanding Panel Image Gallery with Flex-Grow Transitions
- 04Tailwind :has() Hover Spotlight Image Grid
- 05Tailwind Radial Orbit Image Gallery with Counter-Rotating Thumbnails
- 06Tailwind Interactive Pointer Trail Image Gallery
- 07Tailwind Masonry Image Gallery with CSS Columns (No JavaScript)
- 08Tailwind Metro Tile Grid Gallery with Dense Auto-Flow
- 09Tailwind Editorial Magazine Grid Gallery with Blend-Mode Headline
- 10Tailwind Pure CSS Lightbox Gallery with :target (No JavaScript)
- 11Tailwind Filmstrip Carousel Gallery with CSS Scroll-Snap
- 12Tailwind Accordion Image Gallery with Flex-Grow on Hover & Focus
- 13Tailwind Mosaic Grid Gallery with Contained Zoom on Hover
- 14Tailwind Glassmorphism Image Cards with backdrop-blur
- 15Tailwind Diagonal Slice Reveal Gallery with clip-path Halves
- 16Tailwind 3D Tilt Parallax Image Cards with Pointer-Driven Variables
- 17Tailwind 3D Cube Image Viewer with Radio-Driven Rotation
- 18Tailwind 3D Perspective Depth Stack Gallery That Fans on Focus
- 19Tailwind Infinite Scrolling Image Marquee with Seamless Loop
- 20Tailwind Ken Burns Slideshow Gallery with Staggered Cross-Fade
- 21Tailwind Polaroid Photo Stack Gallery with Rotate-on-Hover
- 22Tailwind Scattered Photo Collage with :target Spotlight
Tailwind :has() Hover Spotlight Image Grid
Hover any card and every other card in the grid dims and desaturates, spotlighting the one you are on. The behaviour that used to need a JavaScript mouseenter loop is one relational selector — and in Tailwind it is one arbitrary variant, declared on the grid that owns the relationship.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tgl-04 w-full min-h-svh grid place-items-center bg-tgl04-bg font-tgl04-sans px-5 py-10" aria-label="Spotlight hover gallery">
<div class="w-[min(760px,100%)] grid grid-cols-4 gap-3 max-[560px]:grid-cols-2 [&:has(a:hover)>a:not(:hover)]:opacity-40 [&:has(a:hover)>a:not(:hover)]:saturate-[0.35] [&:has(a:hover)>a:not(:hover)]:brightness-[0.7] [&:has(a:focus-visible)>a:not(:focus-visible)]:opacity-40 [&:has(a:focus-visible)>a:not(:focus-visible)]:saturate-[0.35] [&:has(a:focus-visible)>a:not(:focus-visible)]:brightness-[0.7]">
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1513506003901-1e6a229e2d15?q=80&w=400&h=400&auto=format&fit=crop" alt="Neon-lit Tokyo street" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Tokyo</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1517841905240-472988babdf9?q=80&w=400&h=400&auto=format&fit=crop" alt="Paris rooftops at dusk" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Paris</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1467003909585-2f8a72700288?q=80&w=400&h=400&auto=format&fit=crop" alt="New York skyline" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">New York</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?q=80&w=400&h=400&auto=format&fit=crop" alt="London bridge over the Thames" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">London</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1482938289607-e9573fc25ebb?q=80&w=400&h=400&auto=format&fit=crop" alt="Sydney harbour" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Sydney</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1595152452543-e5fc28ebc2b8?q=80&w=400&h=400&auto=format&fit=crop" alt="Roman architecture" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Rome</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1531891437562-4301cf35b7e4?q=80&w=400&h=400&auto=format&fit=crop" alt="Dubai towers" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Dubai</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1474979266404-7eaacbcd87c5?q=80&w=400&h=400&auto=format&fit=crop" alt="Cairo skyline" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Cairo</span>
</a>
</div>
</section><section class="tgl-04 w-full min-h-svh grid place-items-center bg-tgl04-bg font-tgl04-sans px-5 py-10" aria-label="Spotlight hover gallery">
<div class="w-[min(760px,100%)] grid grid-cols-4 gap-3 max-[560px]:grid-cols-2 [&:has(a:hover)>a:not(:hover)]:opacity-40 [&:has(a:hover)>a:not(:hover)]:saturate-[0.35] [&:has(a:hover)>a:not(:hover)]:brightness-[0.7] [&:has(a:focus-visible)>a:not(:focus-visible)]:opacity-40 [&:has(a:focus-visible)>a:not(:focus-visible)]:saturate-[0.35] [&:has(a:focus-visible)>a:not(:focus-visible)]:brightness-[0.7]">
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1513506003901-1e6a229e2d15?q=80&w=400&h=400&auto=format&fit=crop" alt="Neon-lit Tokyo street" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Tokyo</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1517841905240-472988babdf9?q=80&w=400&h=400&auto=format&fit=crop" alt="Paris rooftops at dusk" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Paris</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1467003909585-2f8a72700288?q=80&w=400&h=400&auto=format&fit=crop" alt="New York skyline" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">New York</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?q=80&w=400&h=400&auto=format&fit=crop" alt="London bridge over the Thames" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">London</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1482938289607-e9573fc25ebb?q=80&w=400&h=400&auto=format&fit=crop" alt="Sydney harbour" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Sydney</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1595152452543-e5fc28ebc2b8?q=80&w=400&h=400&auto=format&fit=crop" alt="Roman architecture" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Rome</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1531891437562-4301cf35b7e4?q=80&w=400&h=400&auto=format&fit=crop" alt="Dubai towers" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Dubai</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1474979266404-7eaacbcd87c5?q=80&w=400&h=400&auto=format&fit=crop" alt="Cairo skyline" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Cairo</span>
</a>
</div>
</section>@import "tailwindcss";
@theme {
--color-tgl04-bg: #0f1013;
--color-tgl04-bg: oklch(0.16 0.006 265);
--color-tgl04-cell: #1a1c20;
--color-tgl04-cell: oklch(0.23 0.008 265);
--color-tgl04-acc: #f0a63a;
--color-tgl04-acc: oklch(0.78 0.15 65);
--font-tgl04-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-04 {
width: 100%;
min-height: 100svh;
}
.tgl-04 ::selection {
background: var(--color-tgl04-acc);
color: #111;
}@import "tailwindcss";
@theme {
--color-tgl04-bg: #0f1013;
--color-tgl04-bg: oklch(0.16 0.006 265);
--color-tgl04-cell: #1a1c20;
--color-tgl04-cell: oklch(0.23 0.008 265);
--color-tgl04-acc: #f0a63a;
--color-tgl04-acc: oklch(0.78 0.15 65);
--font-tgl04-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-04 {
width: 100%;
min-height: 100svh;
}
.tgl-04 ::selection {
background: var(--color-tgl04-acc);
color: #111;
}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-tgl04-bg: #0f1013;
--color-tgl04-bg: oklch(0.16 0.006 265);
--color-tgl04-cell: #1a1c20;
--color-tgl04-cell: oklch(0.23 0.008 265);
--color-tgl04-acc: #f0a63a;
--color-tgl04-acc: oklch(0.78 0.15 65);
--font-tgl04-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-04 {
width: 100%;
min-height: 100svh;
}
.tgl-04 ::selection {
background: var(--color-tgl04-acc);
color: #111;
}@import "tailwindcss";
@theme {
--color-tgl04-bg: #0f1013;
--color-tgl04-bg: oklch(0.16 0.006 265);
--color-tgl04-cell: #1a1c20;
--color-tgl04-cell: oklch(0.23 0.008 265);
--color-tgl04-acc: #f0a63a;
--color-tgl04-acc: oklch(0.78 0.15 65);
--font-tgl04-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-04 {
width: 100%;
min-height: 100svh;
}
.tgl-04 ::selection {
background: var(--color-tgl04-acc);
color: #111;
}<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.
- This is the demo where the port is a deliberate non-port: the relational selector stays a selector, just relocated into the class list. Wrapping it in an arbitrary variant keeps it beside the grid it describes rather than in a stylesheet three files away.
saturate-[0.35]andbrightness-[0.7]compose into onefilterin v4, so the two utilities do not clobber each other the way two hand-writtenfilterdeclarations would.- UI change from the CSS original: the card scale is now suppressed under
motion-reduce:while the dim is kept, so motion-sensitive users still get the spotlight without the grid jumping.
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 :has() Hover Spotlight Image Grid
Source: https://codefronts.com/snippets/tailwind-css-image-gallery/tailwind-has-hover-spotlight-image-grid/
Hover any card and every other card in the grid dims and desaturates, spotlighting the one you are on. The behaviour that used to need a JavaScript mouseenter loop is one relational selector — and in Tailwind it is one arbitrary variant, declared on the grid that owns the relationship.
## HTML
```html
<section class="tgl-04 w-full min-h-svh grid place-items-center bg-tgl04-bg font-tgl04-sans px-5 py-10" aria-label="Spotlight hover gallery">
<div class="w-[min(760px,100%)] grid grid-cols-4 gap-3 max-[560px]:grid-cols-2 [&:has(a:hover)>a:not(:hover)]:opacity-40 [&:has(a:hover)>a:not(:hover)]:saturate-[0.35] [&:has(a:hover)>a:not(:hover)]:brightness-[0.7] [&:has(a:focus-visible)>a:not(:focus-visible)]:opacity-40 [&:has(a:focus-visible)>a:not(:focus-visible)]:saturate-[0.35] [&:has(a:focus-visible)>a:not(:focus-visible)]:brightness-[0.7]">
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1513506003901-1e6a229e2d15?q=80&w=400&h=400&auto=format&fit=crop" alt="Neon-lit Tokyo street" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Tokyo</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1517841905240-472988babdf9?q=80&w=400&h=400&auto=format&fit=crop" alt="Paris rooftops at dusk" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Paris</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1467003909585-2f8a72700288?q=80&w=400&h=400&auto=format&fit=crop" alt="New York skyline" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">New York</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?q=80&w=400&h=400&auto=format&fit=crop" alt="London bridge over the Thames" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">London</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1482938289607-e9573fc25ebb?q=80&w=400&h=400&auto=format&fit=crop" alt="Sydney harbour" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Sydney</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1595152452543-e5fc28ebc2b8?q=80&w=400&h=400&auto=format&fit=crop" alt="Roman architecture" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Rome</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1531891437562-4301cf35b7e4?q=80&w=400&h=400&auto=format&fit=crop" alt="Dubai towers" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Dubai</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1474979266404-7eaacbcd87c5?q=80&w=400&h=400&auto=format&fit=crop" alt="Cairo skyline" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Cairo</span>
</a>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tgl04-bg: #0f1013;
--color-tgl04-bg: oklch(0.16 0.006 265);
--color-tgl04-cell: #1a1c20;
--color-tgl04-cell: oklch(0.23 0.008 265);
--color-tgl04-acc: #f0a63a;
--color-tgl04-acc: oklch(0.78 0.15 65);
--font-tgl04-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-04 {
width: 100%;
min-height: 100svh;
}
.tgl-04 ::selection {
background: var(--color-tgl04-acc);
color: #111;
}
```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 :has() Hover Spotlight Image Grid
Source: https://codefronts.com/snippets/tailwind-css-image-gallery/tailwind-has-hover-spotlight-image-grid/
Hover any card and every other card in the grid dims and desaturates, spotlighting the one you are on. The behaviour that used to need a JavaScript mouseenter loop is one relational selector — and in Tailwind it is one arbitrary variant, declared on the grid that owns the relationship.
## HTML
```html
<section class="tgl-04 w-full min-h-svh grid place-items-center bg-tgl04-bg font-tgl04-sans px-5 py-10" aria-label="Spotlight hover gallery">
<div class="w-[min(760px,100%)] grid grid-cols-4 gap-3 max-[560px]:grid-cols-2 [&:has(a:hover)>a:not(:hover)]:opacity-40 [&:has(a:hover)>a:not(:hover)]:saturate-[0.35] [&:has(a:hover)>a:not(:hover)]:brightness-[0.7] [&:has(a:focus-visible)>a:not(:focus-visible)]:opacity-40 [&:has(a:focus-visible)>a:not(:focus-visible)]:saturate-[0.35] [&:has(a:focus-visible)>a:not(:focus-visible)]:brightness-[0.7]">
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1513506003901-1e6a229e2d15?q=80&w=400&h=400&auto=format&fit=crop" alt="Neon-lit Tokyo street" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Tokyo</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1517841905240-472988babdf9?q=80&w=400&h=400&auto=format&fit=crop" alt="Paris rooftops at dusk" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Paris</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1467003909585-2f8a72700288?q=80&w=400&h=400&auto=format&fit=crop" alt="New York skyline" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">New York</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?q=80&w=400&h=400&auto=format&fit=crop" alt="London bridge over the Thames" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">London</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1482938289607-e9573fc25ebb?q=80&w=400&h=400&auto=format&fit=crop" alt="Sydney harbour" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Sydney</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1595152452543-e5fc28ebc2b8?q=80&w=400&h=400&auto=format&fit=crop" alt="Roman architecture" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Rome</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1531891437562-4301cf35b7e4?q=80&w=400&h=400&auto=format&fit=crop" alt="Dubai towers" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Dubai</span>
</a>
<a href="#" class="relative block rounded-xl overflow-hidden bg-tgl04-cell no-underline transition-[transform,opacity,filter] duration-[400ms] hover:scale-[1.06] hover:z-[2] hover:shadow-[0_20px_40px_-18px_rgba(0,0,0,0.7)] focus-visible:scale-[1.06] focus-visible:z-[2] focus-visible:outline-3 focus-visible:outline-tgl04-acc focus-visible:outline-offset-2 motion-reduce:transition-none motion-reduce:hover:scale-100 motion-reduce:focus-visible:scale-100">
<img src="https://images.unsplash.com/photo-1474979266404-7eaacbcd87c5?q=80&w=400&h=400&auto=format&fit=crop" alt="Cairo skyline" loading="lazy" decoding="async" class="block w-full aspect-square object-cover">
<span class="absolute inset-x-0 bottom-0 px-2.5 pt-5 pb-2 text-white text-xs font-bold tracking-[0.03em] bg-[linear-gradient(0deg,rgba(0,0,0,0.8),transparent)]">Cairo</span>
</a>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tgl04-bg: #0f1013;
--color-tgl04-bg: oklch(0.16 0.006 265);
--color-tgl04-cell: #1a1c20;
--color-tgl04-cell: oklch(0.23 0.008 265);
--color-tgl04-acc: #f0a63a;
--color-tgl04-acc: oklch(0.78 0.15 65);
--font-tgl04-sans: 'Space Grotesk', system-ui, sans-serif;
}
.tgl-04 {
width: 100%;
min-height: 100svh;
}
.tgl-04 ::selection {
background: var(--color-tgl04-acc);
color: #111;
}
```How this works
Some selectors should stay selectors. group-hover: cannot express "every sibling except the hovered one", and Tailwind is right not to invent a variant for it. The arbitrary variant keeps the exact CSS while still living in the markup:
class="grid grid-cols-4 gap-3
[&:has(a:hover)>a:not(:hover)]:opacity-40
[&:has(a:hover)>a:not(:hover)]:saturate-[0.35]
[&:has(a:focus-visible)>a:not(:focus-visible)]:opacity-40"The child combinator matters. >a rather than a descendant a, so a link nested inside a caption could never dim its own card.
Failure is graceful. Without :has() the variant never matches and the grid simply shows every card at full strength — the effect is missing, nothing is broken.
Make it yours
- Dim depth:
opacity-40plussaturate-[0.35]is strong; drop the saturate for a soft fade. - Add
blur-[1px]to the dimmed siblings for a depth-of-field spotlight. - The same variant works on a product row or a nav — nothing about it is image-specific.
Gotchas — read before shipping
- Repeat the whole variant per property.
[&:has(a:hover)>a:not(:hover)]:prefixes each utility; there is no grouping syntax, and that verbosity is the price of keeping it in the markup. - Always write the
:focus-visibletwin. A spotlight that only answers to a mouse is a spotlight half the audience never sees. - Do not dim so far that a caption in a dimmed card drops below 4.5:1 — 40% opacity over a dark stage is about the floor.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
:has() is the gate (Chrome 105+, Safari 15.4+, Firefox 121+) and is Baseline since 2023. Unsupported engines show an undimmed grid.