Back to Blog
Back to Blog
Tutorials·GSAP·April 23, 2026·7 min read

GSAP Stagger: Animate Lists and Grids with Rhythm (2026)

Learn how to use GSAP stagger to animate multiple elements with perfect timing. Covers basic stagger, advanced object syntax, from options, and real-world grid reveals.

GSAP stagger animation tutorial — animating lists and grids with timing offset

Estimated reading time: 8 minutes | Skill level: Beginner to Intermediate

When I animate a list of cards without stagger, it looks wrong. All six cards appear at the same time, like a wall just dropped. The moment I add a stagger, the same animation feels intentional. Each card enters with a slight offset and the whole thing reads as a designed sequence.

Stagger is one of the most impactful things you can add to any list or grid animation. GSAP makes it extremely flexible.

The Basics

The simplest form: a number representing the seconds between each element's animation start.

gsap.from(".card", { y: 30, opacity: 0, duration: 0.6, ease: "power3.out", stagger: 0.1 // each card starts 0.1s after the previous });
GSAP targets all
.card
elements and offsets each one by 0.1 seconds. The first card starts immediately. The second starts at 0.1s. The third at 0.2s. And so on.

This single property transforms a flat entrance into a flowing reveal.

Visual: PLACEHOLDER - Side-by-side of 6 cards appearing without stagger vs with stagger: 0.1

Stagger with a Timeline

Inside a timeline, stagger works exactly the same way. The staggered sequence becomes one step in a larger choreography:

const tl = gsap.timeline({ scrollTrigger: { trigger: ".grid", start: "top 80%", once: true } }); tl.from(".section-title", { y: 20, opacity: 0, duration: 0.7 }); tl.from(".card", { y: 30, opacity: 0, duration: 0.6, ease: "power3.out", stagger: 0.1 }, "-=0.3");

The title animates first. Then the cards cascade in, overlapping the title by 0.3 seconds. The whole section feels like one connected motion.

The Stagger Object

For more control, pass an object instead of a number.

amount vs each

There are two ways to define stagger timing:

// each: fixed offset between items (0.1s per item regardless of count) stagger: { each: 0.1 } // amount: total stagger duration distributed across all items stagger: { amount: 0.6 } // 6 items = 0.1s each; 12 items = 0.05s each
I use
each
when I want consistent rhythm regardless of item count. I use
amount
when I want the total reveal to always take the same amount of time.

from

The
from
option controls which element starts first:
stagger: { each: 0.08, from: "start" } // left to right (default) stagger: { each: 0.08, from: "end" } // right to left stagger: { each: 0.08, from: "center" } // outward from center stagger: { each: 0.08, from: "edges" } // inward from edges to center stagger: { each: 0.08, from: "random" } // randomized order

Random stagger is particularly effective for organic-looking reveals:

gsap.from(".card", { y: 20, opacity: 0, duration: 0.5, stagger: { each: 0.07, from: "random" } });

Elements animate in a scattered order instead of a predictable sequence. Good for grids where the diagonal left-to-right pattern would feel too mechanical.

Visual: PLACEHOLDER - Grid of cards showing 5 different stagger directions: start, end, center, edges, random

Grid-Aware Stagger

For two-dimensional grids, GSAP can calculate stagger based on grid position rather than DOM order. Pass the grid dimensions and GSAP treats the elements as rows and columns:

gsap.from(".card", { y: 30, opacity: 0, duration: 0.6, stagger: { each: 0.05, from: "center", grid: [3, 4] // 3 rows, 4 columns } });
With
from: "center"
and
grid: [3, 4]
, elements radiate outward from the grid's center point. The DOM order doesn't matter. The animation reads as a 2D ripple. You can also use
grid: "auto"
and GSAP will figure out the grid dimensions automatically:
stagger: { each: 0.05, from: "start", grid: "auto" }

This works when your grid items are laid out with CSS Grid or Flexbox and the columns are consistent. GSAP reads the rendered positions.

Function-Based Stagger

For completely custom timing, pass a function. GSAP calls it once per element with the index, element, and full targets array:

gsap.from(".item", { y: 20, opacity: 0, duration: 0.5, stagger: (index) => index * 0.08 + Math.random() * 0.05 });

This produces a base stagger of 0.08s per item with a small random offset added. The result is natural without being chaotic.

A more practical use: different delays based on element type:

gsap.from(".cell", { opacity: 0, scale: 0.9, duration: 0.4, stagger: (index, target) => { // Featured cells animate first return target.classList.contains("featured") ? 0 : (index * 0.06) + 0.3; } });

Practical Examples

Feature Card Grid

A typical use case: animating a row of feature cards when they enter the viewport.

function animateFeatureCards() { const cards = document.querySelectorAll(".feature-card"); gsap.from(cards, { y: 40, opacity: 0, duration: 0.7, ease: "power3.out", stagger: { each: 0.1, from: "start" }, scrollTrigger: { trigger: ".feature-grid", start: "top 75%", once: true } }); }

Navigation Links

Stagger works well for menu items that animate in on page load or menu open:

function animateNavLinks() { gsap.from(".nav-link", { y: -10, opacity: 0, duration: 0.4, ease: "power2.out", stagger: 0.06 }); }

The offset is small (0.06s) because navigation items are close together. Too much stagger on short distances looks slow and disconnected.

List Items with Scroll Trigger

Long lists that reveal as you scroll:

gsap.from(".list-item", { x: -20, opacity: 0, duration: 0.5, ease: "power2.out", stagger: { amount: 0.8, // total 0.8s for all items however many there are from: "start" }, scrollTrigger: { trigger: ".list", start: "top 80%", once: true } });
Using
amount
here means the total stagger is always 0.8 seconds, whether the list has 5 or 20 items.

Stagger with Repeat and Yoyo

Stagger also works on repeated animations. Each element loops with its own offset:

gsap.to(".dot", { y: -15, duration: 0.4, ease: "power2.out", repeat: -1, yoyo: true, stagger: { each: 0.1, from: "start", repeat: -1 } });
This creates a bouncing dots loader where each dot animates in sequence continuously. The
stagger.repeat: -1
keeps the stagger pattern looping rather than the dots gradually syncing up.

Performance Note

When animating many elements with stagger, animate
opacity
and
transform
properties (
x
,
y
,
scale
,
rotation
). These run on the compositor and don't trigger layout. Avoid animating
width
,
height
,
top
,
left
, or
margin
in stagger animations with many targets.

For very long lists (50+ items), consider animating only elements that are currently visible instead of staggering the entire list at once.

Common Mistakes

Stagger that's too large. A stagger of 0.5s on a 10-item grid means the last card doesn't animate for 4.5 seconds. Keep stagger values small: 0.05 to 0.15 seconds works for most use cases.

Forgetting

once: true
on scroll triggers. Without it, the stagger animation replays every time the user scrolls back and forth past the trigger point.

Using stagger on a single element. It's just a delay at that point. Use
delay
instead. Animating layout properties.
width
,
height
,
top
,
left
with stagger on many elements is a guaranteed performance problem. Stick to transforms.

Key Takeaways

  • stagger: 0.1
    offsets each element by 0.1 seconds. Simple and effective.
  • The object syntax gives you
    each
    ,
    amount
    ,
    from
    , and
    grid
    controls.
  • from: "random"
    creates organic-looking reveals for grids.
  • grid: "auto"
    or
    grid: [rows, cols]
    enables 2D ripple patterns.
  • Function-based stagger gives you complete custom timing per element.
  • Keep stagger values small (0.05 to 0.15s) and animate transforms only.

Take It Further

Stagger becomes even more powerful inside timelines. The GSAP Timeline Tutorial covers how to sequence staggered groups as part of larger choreography.

For scroll-triggered stagger, GSAP ScrollTrigger Examples has working examples with batch triggers for improved performance on long pages.

Browse the Annnimate library for production-ready animations that use these stagger patterns. Every animation includes the full GSAP code.

Written by

Julian Fella

Julian Fella

Founder

Related reading

GSAP timeline tutorial showing animation sequencing with gsap.timeline() position parameter
Tutorials·April 20, 2026

GSAP Timeline Tutorial: Sequence Animations Like a Pro (2026)

Learn how to use gsap.timeline() to sequence animations with precision. Covers the position parameter, defaults, labels, playback control, and real-world examples.

Read article
Read article
GSAP ScrollTrigger tutorial showing multiple scroll animation patterns including parallax, reveals, and text effects
Tutorials·April 19, 2026

GSAP ScrollTrigger Examples: 10 Scroll Animations You Can Use Today

Ten production-ready GSAP ScrollTrigger patterns: fade reveals, parallax, text effects, SVG drawing, mask reveals, and flip animations. Copy-paste code for each.

Read article
Read article
GSAP SplitText tutorial showing character and line text animations with scroll triggers
Tutorials·April 17, 2026

GSAP Text Animation: A Practical SplitText Guide (2026)

How to animate text with GSAP SplitText. Covers chars, words, and lines with scroll-triggered reveals, stagger, and mask effects. Copy-paste examples included.

Read article
Read article

On This Page

Stay ahead of the curve

Join 1000+ creators getting animation updates. Unsubscribe anytime.

Animations
Animations
Pricing
Pricing
Blog
Blog
Showcase
Showcase
Changelog
Changelog
Roadmap
Roadmap
XLinkedInInstagram

© 2026 Annnimate · Built by Good Fella

Privacy
Privacy
Terms
Terms
Cookies
Cookies
Refund
Refund