Grid Reveal Scroll Gallery
Original · freeAn image grid whose tiles fade and scale in, staggered, as they scroll into view.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/gallery-grid-reveal-scroll.json"use client";
import { useRef } from "react";
import { motion, useScroll, useTransform } from "motion/react";
type Orientation = "portrait" | "landscape" | "square";
interface Shot {
src: string;
alt: string;
title: string;
category: string;
orientation: Orientation;
}
const SHOTS: Shot[] = [
{
src: "/img/gallery/g03.webp",
alt: "Soft morning light falling across a quiet coastline",
title: "Coastal light",
category: "Landscape",
orientation: "portrait",
},
{
src: "/img/gallery/g11.webp",
alt: "Overhead view of a minimal ceramics studio bench",
title: "Studio 04",
category: "Craft",
orientation: "landscape",
},
{
src: "/img/gallery/g18.webp",
alt: "Detail of dew resting on a fern leaf at dawn",
title: "First frost",
category: "Nature",
orientation: "square",
},
{
src: "/img/gallery/g07.webp",
alt: "A lone figure walking a wide empty beach",
title: "Long shore",
category: "Portrait",
orientation: "portrait",
},
{
src: "/img/gallery/g22.webp",
alt: "Warm interior with sunlight raking across a plaster wall",
title: "Afternoon room",
category: "Interiors",
orientation: "landscape",
},
{
src: "/img/gallery/g14.webp",
alt: "Close crop of layered mountain ridges in haze",
title: "Blue ridges",
category: "Landscape",
orientation: "square",
},
{
src: "/img/gallery/g29.webp",
alt: "Hands folding fabric on a linen-covered table",
title: "Slow craft",
category: "Craft",
orientation: "portrait",
},
{
src: "/img/gallery/g05.webp",
alt: "Golden field grasses bending in low evening wind",
title: "Field study",
category: "Nature",
orientation: "landscape",
},
{
src: "/img/gallery/g33.webp",
alt: "Architectural stair detail catching hard midday shadow",
title: "Concrete rhythm",
category: "Architecture",
orientation: "portrait",
},
{
src: "/img/gallery/g26.webp",
alt: "Still life of fruit and glass on a bright windowsill",
title: "Windowsill",
category: "Still life",
orientation: "square",
},
];
const SPAN: Record<Orientation, string> = {
portrait: "sm:row-span-2",
landscape: "sm:col-span-2",
square: "",
};
const RATIO: Record<Orientation, string> = {
portrait: "aspect-[4/5]",
landscape: "aspect-[13/9]",
square: "aspect-square",
};
export default function GalleryGridRevealScroll() {
const headRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
target: headRef,
offset: ["start end", "end start"],
});
const lineWidth = useTransform(scrollYProgress, [0, 0.6], ["0%", "100%"]);
return (
<section className="relative w-full overflow-hidden bg-white px-5 py-24 text-slate-900 sm:px-8 lg:py-32 dark:bg-neutral-950 dark:text-slate-100">
<style>{`
@keyframes ggrs-float {
0%, 100% { transform: translate3d(0, 0, 0); }
50% { transform: translate3d(0, -14px, 0); }
}
@media (prefers-reduced-motion: reduce) {
.ggrs-orb { animation: none !important; }
}
`}</style>
{/* ambient background */}
<div
aria-hidden
className="ggrs-orb pointer-events-none absolute -top-32 right-[-10%] h-[26rem] w-[26rem] rounded-full bg-gradient-to-br from-indigo-300/40 via-violet-300/30 to-transparent blur-3xl dark:from-indigo-600/20 dark:via-violet-600/15"
style={{ animation: "ggrs-float 11s ease-in-out infinite" }}
/>
<div
aria-hidden
className="ggrs-orb pointer-events-none absolute bottom-[-8rem] left-[-8%] h-[22rem] w-[22rem] rounded-full bg-gradient-to-tr from-emerald-300/30 via-sky-300/20 to-transparent blur-3xl dark:from-emerald-600/15 dark:via-sky-600/10"
style={{ animation: "ggrs-float 13s ease-in-out infinite reverse" }}
/>
<div className="relative mx-auto max-w-6xl">
{/* header */}
<div ref={headRef} className="mx-auto max-w-2xl text-center">
<motion.span
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.6 }}
transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-slate-50 px-4 py-1.5 text-xs font-medium tracking-wide text-slate-600 uppercase dark:border-white/10 dark:bg-white/5 dark:text-slate-300"
>
<span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
Selected work
</motion.span>
<motion.h2
initial={{ opacity: 0, y: 18 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.6 }}
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1], delay: 0.05 }}
className="mt-6 text-balance text-4xl font-semibold tracking-tight sm:text-5xl"
>
Frames that reveal
<span className="bg-gradient-to-r from-indigo-500 via-violet-500 to-emerald-500 bg-clip-text text-transparent">
{" "}
as you look
</span>
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 18 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.6 }}
transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1], delay: 0.12 }}
className="mx-auto mt-5 max-w-lg text-pretty text-base leading-relaxed text-slate-600 dark:text-slate-400"
>
A quiet collection of light, place, and craft. Each tile eases into
view as it enters the frame.
</motion.p>
<motion.div
style={{ width: lineWidth }}
className="mx-auto mt-8 h-px max-w-xs bg-gradient-to-r from-transparent via-slate-300 to-transparent dark:via-white/20"
/>
</div>
{/* grid */}
<div className="mt-14 grid auto-rows-[180px] grid-cols-1 gap-4 sm:grid-cols-2 sm:auto-rows-[200px] lg:grid-cols-3">
{SHOTS.map((shot, i) => (
<motion.figure
key={shot.src}
initial={{ opacity: 0, scale: 0.9, y: 28 }}
whileInView={{ opacity: 1, scale: 1, y: 0 }}
viewport={{ once: true, amount: 0.25, margin: "0px 0px -60px 0px" }}
transition={{
duration: 0.7,
ease: [0.22, 1, 0.36, 1],
delay: (i % 3) * 0.08,
}}
className={`group relative overflow-hidden rounded-2xl bg-slate-100 shadow-sm ring-1 ring-slate-900/5 dark:bg-neutral-900 dark:ring-white/10 ${SPAN[shot.orientation]}`}
>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={shot.src}
alt={shot.alt}
loading="lazy"
draggable={false}
className={`h-full w-full object-cover transition-transform duration-[900ms] ease-[cubic-bezier(0.22,1,0.36,1)] group-hover:scale-[1.06] ${RATIO[shot.orientation]} sm:aspect-auto`}
/>
{/* gradient scrim */}
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-slate-950/70 via-slate-950/5 to-transparent opacity-80 transition-opacity duration-500 group-hover:opacity-95" />
{/* category chip */}
<figcaption className="absolute inset-x-0 bottom-0 flex items-end justify-between gap-3 p-4">
<div className="translate-y-1 transition-transform duration-500 group-hover:translate-y-0">
<p className="text-[10px] font-semibold tracking-[0.14em] text-white/70 uppercase">
{shot.category}
</p>
<p className="mt-0.5 text-base font-medium text-white drop-shadow-sm">
{shot.title}
</p>
</div>
<span
aria-hidden
className="mb-1 flex h-8 w-8 shrink-0 translate-y-2 items-center justify-center rounded-full bg-white/15 text-white opacity-0 backdrop-blur-sm transition-all duration-500 group-hover:translate-y-0 group-hover:opacity-100"
>
<svg
viewBox="0 0 24 24"
fill="none"
className="h-4 w-4"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M7 17 17 7M9 7h8v8" />
</svg>
</span>
</figcaption>
{/* focus/hover ring */}
<span className="pointer-events-none absolute inset-0 rounded-2xl ring-2 ring-indigo-500/0 transition-[box-shadow,ring] duration-500 group-hover:ring-indigo-500/40" />
</motion.figure>
))}
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Hover Caption Gallery
OriginalA responsive image grid where each tile subtly zooms and reveals a caption bar on hover.

Masonry Gallery
OriginalA Pinterest-style masonry gallery using CSS columns with varied-height images; tiles lift and brighten on hover.

Filterable Gallery
OriginalA filterable gallery with category tabs; clicking a tab animates the grid to show only that category.

Tilt Cards Gallery
OriginalA grid of image cards that tilt in 3D toward the cursor on hover, with a soft glare.

Lightbox Gallery
OriginalA thumbnail grid where clicking a thumb opens a full-screen lightbox with prev/next arrows and keyboard support.

Expanding Strip Gallery
OriginalA horizontal strip of image panels; hovering a panel expands it and shrinks the others to reveal its caption.

Marquee Gallery
OriginalTwo rows of images auto-scrolling in opposite directions, pausing on hover, with edge fade masks.

Hover Zoom Overlay Gallery
OriginalA grid where each image scales up under a dark gradient overlay revealing a title and a view button on hover.

Polaroid Stack Gallery
OriginalA scattered stack of slightly-rotated polaroid photos that straighten and lift on hover.
Featured Thumbs Gallery
OriginalA large featured image with a thumbnail row; selecting a thumbnail swaps the featured image with a crossfade.

Carousel Gallery
OriginalAn image carousel with arrows and dots, smooth slide transitions and autoplay that pauses on hover.

Duotone Hover Gallery
OriginalA grid where images sit under a coloured duotone wash that clears to full colour on hover.

