A staggered grid reveal animates a set of cards into view one after another instead of all at once. Portfolio grids, product listings, and team photos all read better when the items cascade in with a short delay between each. Here’s how to build a basic version for WordPress using GSAP and ScrollTrigger, with no page builder add-on required. If you want to compare approaches first, Animate on Scroll: 3 Approaches Compared covers the trade-offs.
What you’ll build
A responsive grid where each item fades and slides up as the grid scrolls into view. The items animate in sequence using GSAP’s grid-aware stagger, so the wave follows the layout rather than raw document order. This basic version reveals once per scroll, adapts to any column count, and respects reduced motion preferences.
Why GSAP for this
You can fake a stagger in CSS by adding an increasing transition-delay to each item, but that approach has limits:
- You hardcode each delay, so adding or removing cards means editing the CSS by hand.
- CSS delays follow document order, not the visual grid, so a diagonal or center-out wave is painful to build.
- There’s no clean scroll trigger; you end up bolting on an Intersection Observer anyway.
GSAP’s stagger accepts a grid option that indexes items by row and column, so the wave follows the layout automatically. Pair it with ScrollTrigger and you get a reveal that fires at the right scroll position with a couple of lines of setup. For the wider CSS versus JavaScript picture, see GSAP vs CSS Animations: A Practical Guide.
Step 1: Add GSAP
GSAP loads from a CDN, so there’s nothing to install. Use a plugin like WPCode or “Insert Headers and Footers” to add these two scripts to your site’s footer:
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/ScrollTrigger.min.js"></script>
The version is pinned to 3.14.2 so a future GSAP release can’t change behavior without you choosing to update.
Step 2: Add the HTML
Add this markup wherever you want the grid. In Gutenberg, use a Custom HTML block. In Elementor or Bricks, use an HTML widget. The data-reveal-grid attribute marks the container and data-reveal-item marks each animated child.
<div class="reveal-grid" data-reveal-grid>
<div class="reveal-card" data-reveal-item>Card 1</div>
<div class="reveal-card" data-reveal-item>Card 2</div>
<div class="reveal-card" data-reveal-item>Card 3</div>
<div class="reveal-card" data-reveal-item>Card 4</div>
<div class="reveal-card" data-reveal-item>Card 5</div>
<div class="reveal-card" data-reveal-item>Card 6</div>
</div>
The cards can hold anything: images, headings, links, or full project cards. The script only cares about the two data attributes.
Step 3: Add the CSS
Add this via your theme’s custom CSS field, the WordPress Customizer, or WPCode as a CSS snippet:
.reveal-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.reveal-card {
aspect-ratio: 4 / 3;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
background: #1a1a1a;
color: #fff;
will-change: transform, opacity;
}
@media (max-width: 700px) {
.reveal-grid {
grid-template-columns: repeat(2, 1fr);
}
}
A couple of notes: grid-template-columns sets the column count, which the stagger reads at runtime, so you don’t repeat the number anywhere in JavaScript. will-change: transform, opacity hints to the browser that these properties will animate, which keeps the reveal on the GPU.
Step 4: Add the JavaScript
Add this script after the two GSAP tags:
document.addEventListener('DOMContentLoaded', function() {
// Respect reduced motion preferences
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
gsap.registerPlugin(ScrollTrigger);
document.querySelectorAll('[data-reveal-grid]').forEach(function(grid) {
var items = grid.querySelectorAll('[data-reveal-item]');
if (!items.length) return;
// Hidden starting state
gsap.set(items, { opacity: 0, y: 40 });
ScrollTrigger.create({
trigger: grid,
start: 'top 80%',
once: true,
onEnter: function() {
gsap.to(items, {
opacity: 1,
y: 0,
duration: 0.8,
ease: 'power3.out',
stagger: {
each: 0.08, // seconds between each item
grid: 'auto', // read rows and columns from the layout
from: 'start' // begin at the top-left item
}
});
}
});
});
});
Here’s what each part does:
gsap.set(items, { opacity: 0, y: 40 })hides the cards and nudges them down before the reveal fires. Setting this in JavaScript rather than CSS avoids a flash of hidden content if the script fails to load; the cards stay visible in that case.ScrollTrigger.create({ start: 'top 80%', once: true })fires the reveal when the top of the grid reaches 80 percent down the viewport, andoncestops it re-triggering on scroll back up.stagger: { grid: 'auto' }tells GSAP to infer the grid shape from the DOM so the delay follows rows and columns. Combined withfrom: 'start', the wave flows from the top-left corner.
Change the wave direction
The from value controls where the stagger originates. Swap it for a different feel:
stagger: {
each: 0.08,
grid: 'auto',
from: 'center' // ripple outward from the middle
}
from accepts 'start', 'end', 'center', 'edges', 'random', or an item index. 'center' gives an explosion-from-the-middle look, and 'end' reverses the flow to start at the bottom-right.
Step 5: Add it to WordPress
You have three pieces: the two GSAP tags (Step 1), the CSS (Step 3), and the JavaScript (Step 4). Here’s how to place them.
Option A: WPCode plugin (recommended)
Snippet 1: CSS (site-wide). Go to Code Snippets > Add Snippet > “Add Your Custom Code”. Set the code type to “CSS Snippet”, paste the CSS from Step 3, toggle on, and save.
Snippet 2: Footer scripts (site-wide). Create another snippet, set the code type to “HTML Snippet”, and paste both GSAP tags followed by the effect script wrapped in a <script> tag:
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/ScrollTrigger.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
gsap.registerPlugin(ScrollTrigger);
document.querySelectorAll('[data-reveal-grid]').forEach(function(grid) {
var items = grid.querySelectorAll('[data-reveal-item]');
if (!items.length) return;
gsap.set(items, { opacity: 0, y: 40 });
ScrollTrigger.create({
trigger: grid,
start: 'top 80%',
once: true,
onEnter: function() {
gsap.to(items, {
opacity: 1,
y: 0,
duration: 0.8,
ease: 'power3.out',
stagger: { each: 0.08, grid: 'auto', from: 'start' }
});
}
});
});
});
</script>
Set the insertion location to “Site Wide Footer”, toggle on, and save.
Snippet 3: Grid HTML (per page). Add the grid markup from Step 2 wherever you need it using a Custom HTML block. The script scans for every [data-reveal-grid] on the page, so you can drop multiple grids on one page and each animates independently.
Option B: Child theme
Add the CSS to your child theme’s style.css. Then enqueue GSAP, ScrollTrigger, and your script through functions.php:
add_action('wp_enqueue_scripts', function() {
wp_enqueue_script('gsap', 'https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js', [], '3.14.2', true);
wp_enqueue_script('scrolltrigger', 'https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/ScrollTrigger.min.js', ['gsap'], '3.14.2', true);
wp_enqueue_script('grid-reveal', get_stylesheet_directory_uri() . '/js/grid-reveal.js', ['gsap', 'scrolltrigger'], '1.0', true);
});
Save the JavaScript from Step 4 as js/grid-reveal.js in your child theme folder. The dependency array keeps the load order correct: GSAP first, then ScrollTrigger, then your script.
Common issues
The whole grid animates before you scroll to it. ScrollTrigger measures position on load. If your grid sits above the fold, it fires immediately, which is expected. For content further down that still triggers too early, tighten the start value from top 80% to top 60%.
Cards flash visible then hide. This happens when the CSS renders before gsap.set runs. Keep the hidden state in JavaScript as shown so the cards only hide once GSAP is confirmed loaded. Do not also hide them with opacity: 0 in CSS, or reduced-motion users will see nothing.
The stagger order looks wrong. grid: 'auto' reads the rendered grid, so it needs the items laid out as a real CSS grid. If your cards use flexbox or floats instead, pass an explicit shape like grid: [2, 3] for two rows and three columns.
Nothing animates after a layout change. If you toggle columns with JavaScript or load cards via AJAX, call ScrollTrigger.refresh() afterward so positions are remeasured.
A caching plugin serves the old page. Clear your cache (WP Rocket, W3 Total Cache, and similar) after adding or editing the snippets.
That’s it
The grid reveals with a staggered wave as it scrolls into view, adapts to your column count, and fires once. Add more data-reveal-grid containers on the same page and each runs on its own. Visitors who prefer reduced motion see the full grid immediately with no animation.
To customize: raise each for a slower cascade, change from for a different wave origin, or swap the y: 40 starting offset for scale: 0.8 to get a pop-in instead of a slide.
Want more control?
The full Stagger Grid Reveal effect includes features this basic version doesn’t have:
- Eight stagger directions including diagonal and center-outward waves, plus
edgesandrandompatterns - Seven animation styles covering fade, scale, flip, slide, blur, rotate, and bounce, each with a tuned from-state
- Wave and spiral stagger patterns for organic motion across larger grids
- ScrollTrigger batch processing that groups items entering the viewport together for smoother performance on long pages
- Interactive hover tilt with 3D perspective on pointer devices
- Configurable grid columns, gap, and item dimensions from data attributes alone
- Responsive breakpoint handling with automatic recalculation when the layout changes
- Lenis smooth scroll compatibility for pages that use smooth scrolling
View the full effect with all options →
Related articles
- Animate on Scroll: 3 Approaches Compared - Compare CSS scroll animations, Intersection Observer, and GSAP ScrollTrigger for reveal effects.
- Adding an Infinite Marquee to WordPress - Another step-by-step WordPress tutorial using GSAP, this time for scrolling strips.
- GSAP vs CSS Animations: A Practical Guide - When CSS transitions are enough and when you need JavaScript animation.
- Adding a Text Scramble Effect to WordPress - A scroll-triggered text reveal you can drop into any WordPress page.