Stagger Reveal Grid
Grid that reveals items with wave-like stagger on scroll. Configurable columns and timing.
Last updated Mar 5, 2026
Component
1
2
3
4
5
6
7
8
9
"use client";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import React, { useLayoutEffect, useRef } from "react";
import { cn } from "@/lib/utils";
gsap.registerPlugin(ScrollTrigger);
interface StaggerRevealGridProps {
children: React.ReactNode;
className?: string;
itemClassName?: string;
columns?: number;
stagger?: number;
duration?: number;
fromY?: number;
fromScale?: number;
fromOpacity?: number;
start?: string;
ease?: string;
}
export function StaggerRevealGrid({
children,
className,
itemClassName,
columns = 3,
stagger = 0.08,
duration = 0.5,
fromY = 40,
fromScale = 0.9,
fromOpacity = 0,
start = "top 85%",
ease = "back.out(1.2)",
}: StaggerRevealGridProps) {
const containerRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
const ctx = gsap.context(() => {
const els =
containerRef.current?.querySelectorAll(
"[data-reveal]",
);
if (!els?.length) {
return;
}
gsap.fromTo(
els,
{
opacity: fromOpacity,
scale: fromScale,
y: fromY,
},
{
duration,
ease,
opacity: 1,
scale: 1,
scrollTrigger: {
start,
trigger: containerRef.current,
},
stagger,
y: 0,
},
);
}, containerRef);
return () => ctx.revert();
}, [
duration,
stagger,
fromY,
fromScale,
fromOpacity,
start,
ease,
]);
return (
<div
ref={containerRef}
className={cn("grid gap-3", className)}
style={{
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
}}
>
{React.Children.map(children, (child, i) =>
child ? (
<div
key={
React.isValidElement(child) &&
child.key != null
? child.key
: `reveal-${i}`
}
data-reveal={true}
className={cn(itemClassName)}
>
{child}
</div>
) : null,
)}
</div>
);
}Installation
pnpm dlx shadcn@latest add "https://pulkitxm.com/components/stagger-reveal-grid.json"1. Install dependencies
pnpm add gsap2. Copy the component file
"use client";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import React, { useLayoutEffect, useRef } from "react";
import { cn } from "@/lib/utils";
gsap.registerPlugin(ScrollTrigger);
interface StaggerRevealGridProps {
children: React.ReactNode;
className?: string;
itemClassName?: string;
columns?: number;
stagger?: number;
duration?: number;
fromY?: number;
fromScale?: number;
fromOpacity?: number;
start?: string;
ease?: string;
}
export function StaggerRevealGrid({
children,
className,
itemClassName,
columns = 3,
stagger = 0.08,
duration = 0.5,
fromY = 40,
fromScale = 0.9,
fromOpacity = 0,
start = "top 85%",
ease = "back.out(1.2)",
}: StaggerRevealGridProps) {
const containerRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
const ctx = gsap.context(() => {
const els =
containerRef.current?.querySelectorAll(
"[data-reveal]",
);
if (!els?.length) {
return;
}
gsap.fromTo(
els,
{
opacity: fromOpacity,
scale: fromScale,
y: fromY,
},
{
duration,
ease,
opacity: 1,
scale: 1,
scrollTrigger: {
start,
trigger: containerRef.current,
},
stagger,
y: 0,
},
);
}, containerRef);
return () => ctx.revert();
}, [
duration,
stagger,
fromY,
fromScale,
fromOpacity,
start,
ease,
]);
return (
<div
ref={containerRef}
className={cn("grid gap-3", className)}
style={{
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
}}
>
{React.Children.map(children, (child, i) =>
child ? (
<div
key={
React.isValidElement(child) &&
child.key != null
? child.key
: `reveal-${i}`
}
data-reveal={true}
className={cn(itemClassName)}
>
{child}
</div>
) : null,
)}
</div>
);
}3. Import and use
import { StaggerRevealGrid } from "@/components/stagger-reveal-grid";
<StaggerRevealGrid columns={3}>
{/* grid items */}
</StaggerRevealGrid>;Usage
Import
Add the StaggerRevealGrid import.
import { StaggerRevealGrid } from "@/components/stagger-reveal-grid";Use
Use with columns and children.
<StaggerRevealGrid columns={3}>
{/* items */}
</StaggerRevealGrid>;Guidelines
- Use columns to set grid columns. stagger and duration control reveal timing.
- start and end control scroll range (ScrollTrigger).
Props
All props are optional unless marked required. Use these to customize every aspect of the component.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Additional CSS classes. |
| columns | number | 3 | Number of grid columns. |
| stagger | number | 0.05 | Delay between each item reveal (seconds). |
| duration | number | 0.5 | Reveal animation duration per item. |
| fromY | number | 40 | Initial Y offset (px) before reveal. |
| fromScale | number | 0.9 | Initial scale before reveal. |
| fromOpacity | number | 0 | Initial opacity before reveal. |
| itemClassName | string | — | Classes for each grid item wrapper. |
| start | string | "top 85%" | ScrollTrigger start. |
| ease | string | "back.out(1.2)" | GSAP ease for reveal. |
Examples
Basic
3-column grid with numbered items.
1
2
3
4
5
6
7
8
9
import { StaggerRevealGrid } from "@/components/stagger-reveal-grid";
<StaggerRevealGrid columns={3}>
{[1, 2, 3, 4, 5, 6].map((n) => (
<div
key={n}
className="aspect-square rounded-xl border bg-white/5"
>
{n}
</div>
))}
</StaggerRevealGrid>;