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.

sequence.ts
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:

PropertyTypeDefaultDescription
targetTargetInputCSS selector, Element, NodeList, or Element[]
fromAnimationVarsStart values
toAnimationVarsEnd values
durationnumber0.5Seconds for this step
delaynumber0Seconds to wait before this step’s own start
easestring"power1.inOut"Easing for this step
staggernumber | StaggerVarsPer-element stagger when target matches multiple elements
positionnumber | stringWhen 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.

ValueMeaning
0.50.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:

sequential.ts
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:

overlapping.ts
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:

parallel.ts
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:

absolute.ts
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:

gap.ts
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:

scroll-sequence.ts
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:

typescript
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