Timeline

gsap.timeline()

gsap.timeline() creates a sequenced container for multiple tweens. Each child tween starts when the previous one ends by default, but the position parameter, labels, and offsets let you overlap, gap, or schedule them anywhere on the timeline. Timelines reverse, pause, resume, and seek as a single unit, which makes them the right tool for any multi-step animation longer than a single tween.

Updated July 8, 2026

Mechanics

How timelines work

Create a timeline with gsap.timeline(), then chain .to(), .from(), .fromTo(), .set() calls. Each call appends a tween that starts when the previous one ends. The timeline's .play(), .pause(), .reverse(), .seek(), .timeScale() control the whole sequence.

script.js

The defaults option sets shared properties for every child tween so you don't repeat ease and duration. easeReverse: true (GSAP 3.15+) keeps the .out feel intact when the timeline plays in reverse, which solves the canonical 'open feels right, close feels wrong' problem.

One playhead, less jank

Sequencing on a single gsap.timeline() gives every tween one shared playhead, so related motion stays coordinated instead of competing. That matters for performance: the HTTP Archive Web Almanac found non-composited, jank-prone animations on roughly 40% of mobile pages, and driving transforms from one timeline keeps that work on the compositor thread.
Position

The position parameter

The second-to-last argument on any .to() / .from() / .set() is the position parameter. It controls WHERE on the timeline the tween starts relative to the previous one.

  • -=0.3 -> start 0.3s before the previous tween ends (overlap)
  • '<' -> start at the previous tween's start time (parallel)
  • '<+0.2' -> start 0.2s after the previous tween's start
  • '>' -> start when the previous tween ends (default, explicit)
  • 1.5 -> start at absolute time 1.5s on the timeline
  • 'label' -> start at a named label set with tl.addLabel('label')
When

Use timelines for

  • Multi-step open/close sequences (drawer, modal, menu)
  • Hero reveals with overlapping headline, subhead, and CTA stages
  • Coordinated stagger across multiple groups that share an overall rhythm
  • Anything you want to reverse, scrub, or seek as a single unit
  • Choreography across N elements where time relationships matter more than per-tween config
Alternatives

Use something else when

  • A single tween - gsap.to() is shorter and clearer
  • Truly parallel independent tweens with no shared rhythm - separate gsap.to() calls
  • Scroll-driven sequencing where position is scroll position, not time - use ScrollTrigger with scrub: true on a single tween or timeline
  • Two-state hover (in/out) where you want overwrite semantics - use overwrite: 'auto' on individual tweens
In production

Used in these Annnimate components

  • The Multi Flip component drives its orchestrated flip sequence from a single timeline with per-card position parameters
  • The Magnetic Button uses a paired open/close timeline so the rest-state animation matches the hover-state choreography
  • The Mega Menu runs two timelines (open and close) with distinct easings rather than reversing one - the close needs different mechanics than the open
  • The Dual Scramble timeline coordinates the underline expand with the character scramble so both finish at the same beat
Used in components

See it running in production

FAQ

Common questions

When should I use a timeline vs separate tweens?
Use a timeline when you want to treat the whole sequence as one unit (reverse it, pause it, scrub it). Use separate tweens when each one stands alone and you don't need shared playback control. The cost of starting with a timeline is near zero, so when in doubt - timeline.
How do I reverse a timeline cleanly?
Call tl.reverse() and ScrollTrigger plus the timeline handle the rewind. For the timeline to feel right in reverse, set easeReverse: true on the timeline options (GSAP 3.15+) so eases stay .out in both directions. Without it, an .out ease played backwards becomes an .in ease, which feels sluggish.
Can timelines have nested timelines?
Yes - pass a timeline as a child of another with .add(childTl). Useful when you want one timeline's logic encapsulated (e.g. an openPanel(id) function returns its own timeline) and you compose them under a parent that handles the overall reveal.
Why doesn't my timeline play in React?
Either you created it outside useGSAP (and React threw it away on re-render), or you forgot to call .play() on a timeline with paused: true. Build the timeline inside useGSAP, store it in a ref, and call .play() / .reverse() from event handlers wrapped in contextSafe.
Should I rebuild a timeline on every open, or build once and reuse?
Build once if positions are static. Rebuild if the timeline depends on dynamic values (current element widths, scroll position at the moment of open). Rebuilding is cheap; the bug is building once with stale measurements and watching the animation drift after a resize.