GSAP easeReverse: The One-Line Fix Every Modal Needs
GSAP 3.15 added easeReverse, a property that fixes how easing behaves on reverse() animations. Here's why it matters and how to use it.

Estimated reading time: 6 minutes | Skill level: Intermediate
When a modal opens with a bounce, then closes by reversing the same animation, something feels off. The exit drags. The bounce is gone. Most developers paper over this by writing two separate animations. One for open, one for close. Different easing on each. It works. It's also twice the code.
GSAP 3.15 fixed this with one property:easeReverseThe actual problem
When you callreverse()expo.outexpo.inFor motion that should ease out in both directions (like a menu that pops open AND pops back), reversing time is the wrong tool. The exit feels weak. People notice without being able to name what's off.
How easeReverse works
You passeaseReverseeasegsap.to(".modal", {
y: 0,
opacity: 1,
ease: "back.out(1.7)",
easeReverse: true,
});
trueYou can also pass a different ease string:
gsap.to(".modal", {
y: 0,
opacity: 1,
ease: "back.out(1.7)",
easeReverse: "sine.in",
});
This is the pattern I reach for on menus and drawers. Bounce in, ease out softly. The two directions don't have to match.
Where this actually matters
Three patterns where the asymmetry shows up most:
Toggleable overlays. Modals, drawers, menus, tooltips. You want a punchy entrance and a clean exit.
const menuTl = gsap.timeline({
paused: true,
defaults: { ease: "expo.out", easeReverse: "expo.in" }
});
menuTl.to(".menu-bg", { yPercent: 0, duration: 0.6 })
.to(".menu-link", { y: 0, opacity: 1, stagger: 0.05 }, "-=0.4");
openButton.addEventListener("click", () => menuTl.play());
closeButton.addEventListener("click", () => menuTl.reverse());
defaultsexpo.outexpo.inback.outeaseReverse: "power3.out"Interrupted animations. If a user toggles a menu before the previous animation finishes, GSAP recalculates the curve from the exact frame the playhead changed direction. No glitch. No reset.
Timeline defaults
The pattern that scales: seteaseReverseconst tl = gsap.timeline({
defaults: {
ease: "back.out(1.7)",
easeReverse: "expo.in"
}
});
tl.to(".hero", { y: -100 })
.to(".overlay", { opacity: 1, easeReverse: "power4.out" })
.to(".cta", { scale: 1 }, 0.2);
tl.reverse();
.overlayeaseReverseeaseReverseyoyoEase is dead
Before 3.15, the closest thing wasyoyoEaseyoyo: truereverse()yoyoEaseeaseReverseWhen not to use it
If your animation only ever plays forward, you don't need this. Skip it.easeReversereverse()easeReverseKey takeaways
- plays time backwards, which inverts your ease curve. That's the real reason reversed animations feel off.
reverse() - reuses your forward ease.
easeReverse: truelets you pick a different one.easeReverse: "ease.name" - Set it in so every child tween inherits it.
timeline.defaults - Deprecates . Use
yoyoEaseinstead.easeReverse - Most useful for toggleable UI: modals, menus, drawers, hover states with overshoot.
Build it without the boilerplate
Most of the menu, button, and overlay animations in the Annnimate library already use the open-then-reverse pattern. With GSAP 3.15 andeaseReverseFor a deeper look at the play/reverse pattern, the GSAP Timeline Tutorial covers timeline defaults and playback control in detail. And the GSAP Hover Effects guide shows where this asymmetry hits hardest.
Written by
Julian Fella
Founder
Related reading

GSAP Page Transitions in Next.js: A Practical Guide (2026)
Learn how to build smooth GSAP page transitions in Next.js App Router. Covers overlay animations, exit animations, useGSAP cleanup, and common pitfalls.

GSAP Hover Effects: 5 Patterns Worth Knowing (2026)
Learn how to build GSAP hover effects that feel polished. Covers play/reverse pattern, magnetic effects, quickTo for mouse tracking, and React implementation.

GSAP Stagger: Animate Lists and Grids with Rhythm (2026)
Learn how to use GSAP stagger to animate multiple elements with perfect timing. Covers basic stagger, advanced object syntax, from options, and real-world grid reveals.