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
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.
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.
The easeEach default catches people out
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.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
0to1.15to1) instead of trusting aback.outcurve - Anywhere you'd otherwise chain three
.to()calls with hand-tuneddelayvalues on one element
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 (
onCompleteper 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
@keyframesis 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.
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
0to1.15to1step list keyframes is built for
See it running in production
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
easeon 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 toeaseEach, which defaults topower1.inOut. The tween-leveleaseacts as an overlay across the whole sequence. - How do I add a pause or hold between keyframes?
- Add a
delayto the step that should wait. In the array form adelayinserts 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
easeEachdefault, 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.
