Timeline

keyframes

keyframes is a GSAP tween property that runs several sequential steps inside a single tween, so you can choreograph multi-stage motion without building a full timeline. You pass an array of vars objects (or a percentage-keyed object) and GSAP plays them back to back, each step carrying its own duration and ease.

Updated July 1, 2026

Mechanics

How keyframes works

The most common form is an array. Each element is a vars object describing one step, and GSAP plays them in order. Every step can set its own duration and ease, and a delay on a step inserts a hold before it starts.

script.js

There is also a percentage-keyed object form (GSAP 3.9+) that reads closer to CSS @keyframes. Here the tween's duration is the total time and each key is a point along it. easeEach sets the ease used between each keyframe and defaults to power1.inOut, so even steps with no explicit ease get smoothed.

script.js

The easeEach default catches people out

Because easeEach defaults to power1.inOut, a sequence you expect to run at a constant rate will slow down and speed up between every step. If you want strictly linear motion through the steps, set easeEach: "none" on the keyframes.
When

Use it for

  • A single element moving through a fixed path (drop in, slide across, settle) where every step hits the same target
  • Per-element multi-beat motion inside a stagger (each item runs the same 3-step sequence, offset in time)
  • Overshoot-and-settle beats you want to spell out explicitly (scale 0 to 1.15 to 1) instead of trusting a back.out curve
  • Anywhere you'd otherwise chain three .to() calls with hand-tuned delay values on one element
Alternatives

Use something else when

  • The steps need to overlap or nest, or you want to seek/pause an individual beat, use a gsap.timeline() with the position parameter
  • Different steps target different elements, a timeline is clearer than cramming it into one tween
  • You need callbacks fired between beats (onComplete per step), a timeline gives you a call at any point
  • It's a single move with one ease, a plain gsap.to() is less to read
  • It's an infinite decorative loop with no JS control (a background shimmer), CSS @keyframes is lighter and stays off the main thread

That CSS route is what most sites reach for. The HTTP Archive Web Almanac 2024 found 91.7% of mobile pages use a CSS transition while only 18.4% load a JS animation library at all. GSAP keyframes earn their place when the sequence needs per-step eases, holds, or runtime control that a CSS declaration can't give you.

In production

Where keyframes fits in Annnimate components

The keyframes property is the compact way to author the per-element beat sequences these components run. Both currently drive their per-element motion through a staggered timeline, and keyframes is the tool for the same job when each item repeats an identical multi-step beat:

  • The Multi Flip card motion is a per-element sequence (flip, travel, land) that keyframes can express as one tween per card inside the stagger, instead of separate steps
  • The Popping Text character pop is an overshoot-and-settle beat, exactly the scale 0 to 1.15 to 1 step list keyframes is built for
script.js
Used in components

See it running in production

FAQ

Common questions

What's the difference between GSAP keyframes and a timeline?
keyframes packs a sequence of steps into ONE tween on ONE set of targets. A timeline is a container that sequences MANY tweens, often across different elements, with labels, overlap via the position parameter, and its own play/pause/seek controls. Reach for keyframes when a single element (or a stagger of identical elements) runs a fixed step sequence. Reach for a timeline when steps overlap, target different elements, or need individual control.
How do I set a different ease for each keyframe step?
Put an ease on the individual step's vars object. In the array form, { x: 200, duration: 0.5, ease: "power2.out" } eases just that step. Any step without its own ease falls back to easeEach, which defaults to power1.inOut. The tween-level ease acts as an overlay across the whole sequence.
How do I add a pause or hold between keyframes?
Add a delay to the step that should wait. In the array form a delay inserts a gap before that step runs, so the previous state holds for that long. There's no separate 'hold' step needed.
Can keyframe steps overlap each other?
No. Array keyframes are strictly sequential, each step starts after the previous one finishes (plus any delay). If you need beats to overlap or run in parallel, that's the case a gsap.timeline() with the position parameter is built for.
Do GSAP keyframes work like CSS @keyframes?
The percentage-object form reads similarly, but GSAP keyframes run in JS, so you get per-step eases, the easeEach default, relative values, function-based values, and full runtime control (pause, reverse, seek). CSS @keyframes is still the lighter choice for an infinite decorative loop that never needs JS control.