Multi-Step Sequencing
Build complex timelines with multiple steps and the position parameter.
Multi-step timelines let you chain animations on different targets into a single, coordinated sequence. Each step runs on its own element with its own timing, and the position parameter gives you precise control over when each step starts relative to the others.
The array config format
Pass an array of animation entries as the second argument instead of a target and config object. Each entry can target a different element and carry its own from, to, duration, and ease.
import { Motion } from "@motion.page/sdk";
Motion("intro", [
{ target: ".hero-title", from: { opacity: 0, y: 40 }, duration: 0.7, ease: "power2.out" },
{ target: ".hero-subtitle", from: { opacity: 0, y: 30 }, duration: 0.6, ease: "power2.out" },
{ target: ".hero-cta", from: { opacity: 0, y: 20 }, duration: 0.5, ease: "power2.out" },
]).onPageLoad(); By default, each step begins immediately after the previous one ends — a pure sequential chain.
Animation entry shape
Every entry in the array accepts the same properties as a single AnimationConfig, plus target and position:
| Property | Type | Default | Description |
|---|---|---|---|
target | TargetInput | — | CSS selector, Element, NodeList, or Element[] |
from | AnimationVars | — | Start values |
to | AnimationVars | — | End values |
duration | number | 0.5 | Seconds for this step |
delay | number | 0 | Seconds to wait before this step’s own start |
ease | string | "power1.inOut" | Easing for this step |
stagger | number | StaggerVars | — | Per-element stagger when target matches multiple elements |
position | number | string | — | When this step starts — see position syntax below |
Position syntax
position controls where in the timeline a step is placed. When omitted, steps play back-to-back.
| Value | Meaning |
|---|---|
0.5 | 0.5 s from the timeline start (absolute time) |
"+=0.5" | 0.5 s after the previous step ends |
"-=0.3" | 0.3 s before the previous step ends (overlap) |
">" | Immediately after the previous step ends (same as default) |
"<" | Same start time as the previous step (parallel) |
"<0.2" | 0.2 s after the start of the previous step |
">-0.1" | 0.1 s before the end of the previous step |
Sequential timing
The default behaviour — each step waits for the previous one to finish:
Motion("sequential", [
{ target: ".box-a", from: { x: -100, opacity: 0 }, duration: 0.5, ease: "power2.out" },
{ target: ".box-b", from: { x: -100, opacity: 0 }, duration: 0.5, ease: "power2.out" },
{ target: ".box-c", from: { x: -100, opacity: 0 }, duration: 0.5, ease: "power2.out" },
]).onPageLoad();
// Total duration: 1.5 s (0.5 + 0.5 + 0.5) Overlapping steps
Use "-=N" to pull a step back in time so it overlaps the one before it. This tightens the overall feel without making things feel simultaneous:
Motion("staggered-overlap", [
{ target: ".card-1", from: { opacity: 0, y: 50 }, duration: 0.6, ease: "power2.out" },
{ target: ".card-2", from: { opacity: 0, y: 50 }, duration: 0.6, ease: "power2.out", position: "-=0.4" },
{ target: ".card-3", from: { opacity: 0, y: 50 }, duration: 0.6, ease: "power2.out", position: "-=0.4" },
]).onPageLoad();
// Total duration: 1.0 s — each card starts 0.2 s after the previous Parallel steps
"<" starts a step at exactly the same time as the previous one. Use it to animate multiple properties or elements simultaneously:
Motion("hero-entrance", [
// Title fades + slides up
{ target: ".title", from: { opacity: 0, y: 40 }, duration: 0.8, ease: "power3.out" },
// Image scales in at the same moment
{ target: ".image", from: { opacity: 0, scale: 0.9 }, duration: 0.8, ease: "power2.out", position: "<" },
// Badge appears 0.3 s after both start
{ target: ".badge", from: { opacity: 0, scale: 0 }, duration: 0.4, ease: "back.out", position: "<0.3" },
]).onPageLoad(); Absolute positioning
An absolute number places a step at a fixed point in the timeline, regardless of what came before:
Motion("timed-sequence", [
{ target: ".step-1", from: { opacity: 0 }, duration: 0.5 },
// Always starts at exactly 1 s, no matter what step-1 does
{ target: ".step-2", from: { opacity: 0 }, duration: 0.5, position: 1 },
// Always starts at exactly 2 s
{ target: ".step-3", from: { opacity: 0 }, duration: 0.5, position: 2 },
]).onPageLoad(); Adding a gap between steps
"+=N" adds extra breathing room after the previous step ends:
Motion("with-pauses", [
{ target: ".reveal-1", from: { opacity: 0, y: 30 }, duration: 0.6, ease: "power2.out" },
// 0.3 s pause, then the next step plays
{ target: ".reveal-2", from: { opacity: 0, y: 30 }, duration: 0.6, ease: "power2.out", position: "+=0.3" },
]).onPageLoad(); Mixing targets and triggers
Multi-step timelines work with every trigger method. A scroll-triggered sequence, for example:
Motion("features", [
{ target: ".feature-icon", from: { opacity: 0, scale: 0.6 }, duration: 0.5, ease: "back.out" },
{ target: ".feature-title", from: { opacity: 0, x: -20 }, duration: 0.4, ease: "power2.out", position: "<0.15" },
{ target: ".feature-body", from: { opacity: 0, x: -20 }, duration: 0.4, ease: "power2.out", position: "<0.1" },
]).onScroll({ scrub: false, toggleActions: "play none none none" }); Tips
Each step re-reads natural CSS. The from/to resolution rules apply per entry — if you only supply from, the SDK reads the element’s current computed style as to. See Duration & Delay for timing basics.
Per-step stagger works normally. If a step’s target matches multiple elements, add stagger to that entry and it fans out exactly as in a single-animation config.
delay inside an entry is local. delay on an entry shifts that step’s start relative to its position, not relative to the global timeline. Use position for timeline-level placement and delay for fine-tuning within a step.
Looping the whole sequence. Chain .withRepeat(-1) after the Motion(...) call to loop the entire multi-step timeline indefinitely:
Motion("looping-sequence", [
{ target: ".dot-1", to: { scale: 1.4 }, duration: 0.3, ease: "power2.out" },
{ target: ".dot-2", to: { scale: 1.4 }, duration: 0.3, ease: "power2.out" },
{ target: ".dot-3", to: { scale: 1.4 }, duration: 0.3, ease: "power2.out" },
])
.withRepeat({ times: -1, yoyo: true })
.onPageLoad(); Related: Duration & Delay · Stagger · Core Concepts