Stagger

Animate multiple targets in sequence with configurable delay, direction, and grid patterns.

stagger adds a per-element delay offset when animating multiple targets. Instead of all elements moving at once, each one starts slightly after the previous — creating a cascade effect.

Basic Stagger

Pass a number directly to stagger. Each element waits that many seconds more than the one before it.

typescript
import { Motion } from "@motion.page/sdk";

Motion("list", ".item", {
  from: { opacity: 0, y: 20 },
  duration: 0.5,
  stagger: 0.08,
  ease: "power2.out",
}).onPageLoad();

Element 1 starts at 0s, element 2 at 0.08s, element 3 at 0.16s, and so on.

StaggerVars Object

For full control, pass an object instead of a number. The shorthand stagger: 0.08 is equivalent to stagger: { each: 0.08, from: 'start' }.

PropertyTypeDefaultDescription
eachnumberSeconds between each element
amountnumberTotal spread across all elements (alternative to each)
fromstring | number'start'Direction or origin of the stagger
grid'auto' | [number, number]Enable 2D grid staggering
axis'x' | 'y'Axis to measure distance for grid stagger
easestring'none'Easing for stagger delay distribution

each vs amount

Use each when you care about the gap between adjacent elements. Use amount when you care about the total time the full stagger takes regardless of element count.

typescript
// Always 0.1s between each element — total time grows with more elements
stagger: { each: 0.1 }

// Always 1s total spread — 10 elements = 0.1s each, 20 elements = 0.05s each
stagger: { amount: 1 }

from — Stagger Origin

from controls which element starts first and how the cascade radiates outward.

ValueDescription
'start'First element leads (default)
'end'Last element leads — sequence runs in reverse
'center'Middle element leads, radiates outward
'edges'Both ends lead simultaneously, meet in the middle
'random'Random order on every play
numberA specific element index leads (0-based) — e.g. from: 2 starts from the third element
typescript
// Default — first to last
Motion("list-start", ".item", {
  from: { opacity: 0, y: 16 },
  duration: 0.5,
  stagger: { each: 0.07, from: "start" },
  ease: "power2.out",
}).onPageLoad();

// Reverse — last to first
Motion("list-end", ".item", {
  from: { opacity: 0, y: 16 },
  duration: 0.5,
  stagger: { each: 0.07, from: "end" },
  ease: "power2.out",
}).onPageLoad();

// Center outward — good for symmetrical layouts
Motion("list-center", ".item", {
  from: { opacity: 0, scale: 0.8 },
  duration: 0.4,
  stagger: { each: 0.06, from: "center" },
  ease: "back.out",
}).onPageLoad();

// Edges inward — both sides converge to the middle
Motion("list-edges", ".item", {
  from: { opacity: 0 },
  duration: 0.4,
  stagger: { each: 0.06, from: "edges" },
}).onPageLoad();

// Random — each play has a different order
Motion("list-random", ".item", {
  from: { opacity: 0, scale: 0.9 },
  duration: 0.4,
  stagger: { each: 0.05, from: "random" },
}).onPageLoad();

// Specific index — ripple from the third card
Motion("list-from-index", ".card", {
  from: { opacity: 0, scale: 0.85 },
  duration: 0.5,
  stagger: { each: 0.06, from: 2 },
  ease: "power2.out",
}).onClick({ each: true });

ease — Stagger Distribution

ease controls how the delay offsets are distributed across elements — not the easing of the animation itself. Without it, delays are evenly spaced. With it, elements bunch up at the start, end, or middle of the sequence.

typescript
// Delays accelerate — early elements are close together, later ones spread out
Motion("ease-in-stagger", ".dot", {
  from: { opacity: 0, y: 20 },
  duration: 0.4,
  stagger: { each: 0.08, ease: "power2.in" },
}).onPageLoad();

// Delays decelerate — quick burst at the start, trailing off
Motion("ease-out-stagger", ".dot", {
  from: { opacity: 0, y: 20 },
  duration: 0.4,
  stagger: { each: 0.08, ease: "power2.out" },
}).onPageLoad();

grid — 2D Stagger Patterns

Grid stagger measures the 2D distance from the origin element, creating ripple or wave effects across rows and columns.

Set grid: 'auto' and the SDK infers the grid dimensions from the layout of elements in the DOM. For irregular or custom grids, pass explicit [rows, columns].

typescript
// Ripple from center of a CSS grid
Motion("grid-ripple", ".cell", {
  from: { opacity: 0, scale: 0.6 },
  duration: 0.4,
  stagger: {
    each: 0.04,
    from: "center",
    grid: "auto",
  },
  ease: "power2.out",
}).onPageLoad();

// Explicit 3×4 grid, wave from top-left
Motion("grid-wave", ".tile", {
  from: { opacity: 0, y: 20 },
  duration: 0.5,
  stagger: {
    each: 0.05,
    from: "start",
    grid: [3, 4],
  },
  ease: "power2.out",
}).onPageLoad();

axis — Directional Grid Waves

Combine grid with axis to constrain stagger distance to a single axis — producing row-by-row or column-by-column waves instead of true radial ripples.

typescript
// Animate row by row (top to bottom)
Motion("row-wave", ".cell", {
  from: { opacity: 0, y: 12 },
  duration: 0.4,
  stagger: {
    each: 0.05,
    grid: "auto",
    axis: "y",
  },
  ease: "power2.out",
}).onPageLoad();

// Animate column by column (left to right)
Motion("col-wave", ".cell", {
  from: { opacity: 0, x: 12 },
  duration: 0.4,
  stagger: {
    each: 0.05,
    grid: "auto",
    axis: "x",
  },
  ease: "power2.out",
}).onPageLoad();

Common Patterns

Staggered Scroll Reveal

typescript
Motion("cards", ".card", {
  from: { opacity: 0, y: 40 },
  duration: 0.6,
  stagger: 0.1,
  ease: "power2.out",
}).onScroll({ scrub: false, toggleActions: "play none none none" });

Text Reveal with Stagger

Split text into words or characters and stagger them for a dramatic entrance.

typescript
Motion("headline", "h1", {
  split: "words",
  mask: true,
  from: { y: "110%" },
  duration: 0.6,
  stagger: { each: 0.06, from: "start" },
  ease: "power3.out",
}).onPageLoad();

See Text Splitter for the full text-splitting API.

Grid Ripple on Hover

typescript
Motion("grid-hover", ".cell", {
  to: { scale: 1.1, backgroundColor: "#6633EE" },
  duration: 0.3,
  stagger: {
    each: 0.03,
    from: "center",
    grid: "auto",
  },
  ease: "power2.out",
}).onHover({ onLeave: "reverse" });

Scene Delay + Stagger

Add a base delay to pause before the staggered sequence begins. The stagger offsets stack on top.

typescript
// Element 1 starts at 0.3s
// Element 2 starts at 0.38s
// Element 3 starts at 0.46s
Motion("delayed-list", ".item", {
  from: { opacity: 0, x: -20 },
  duration: 0.5,
  delay: 0.3,
  stagger: 0.08,
  ease: "power2.out",
}).onPageLoad();

See Duration & Delay for how delay and stagger interact.

API Reference

typescript
// Shorthand — seconds between each element
stagger?: number

// Full object
interface StaggerVars {
  each?:   number;                                              // Seconds between each element
  amount?: number;                                              // Total spread across all elements
  from?:   'start' | 'end' | 'center' | 'edges' | 'random' | number;
  grid?:   'auto' | [number, number];                          // 2D grid stagger
  axis?:   'x' | 'y';                                         // Grid stagger axis
  ease?:   string;                                              // Easing for delay distribution
}

Related: Duration & Delay · Text Splitter · Easing