Two animation engines, one decision tree. Choose the wrong tool and your React component timeline becomes a ScrollTrigger footgun.
Velocity X ships both Framer Motion and GSAP. They're both good. They solve completely different problems. And the moment you reach for the wrong one, you'll know it — because you'll be fighting the library instead of building with it.
The false dichotomy is that you pick one and live with it. That's not how animation actually works. Component-level animations (modals opening, buttons responding to hover, drawers sliding in) are one class of problem. Scroll-driven sequences (hero reveals tied to viewport entry, parallax sections, pinned progress) are another. The best Velocity sites use both, switching engines based on what the animation actually does.
**Framer Motion lives in the component tree.** You declare animation states alongside React state, wrap components with AnimatePresence to handle mounting and unmounting, and let motion handle the choreography. The syntax is declarative — you describe what you want, and Framer Motion figures out the mechanics. It handles layout animations (think a grid where items rearrange and the surrounding elements flow around them), gesture-driven animations (drag, hover, tap), and exit animations that don't just cut when a component unmounts. AnimatePresence is the magic here — it holds a component in the DOM long enough for the exit animation to finish, then removes it. Try doing that cleanly in GSAP without managing refs and timelines manually. You can, but it's verbose.
The strength is ergonomics. If your animation lives inside a component — a toast notification sliding in from the corner, a modal with a backdrop fade, a button with a scale-on-hover effect — Framer is the natural choice. The syntax is React-like. The component unmounts, and the animation cleans up automatically. No manual cleanup, no ScrollTrigger refs left hanging around.
**GSAP lives outside the component tree.** You scope to a DOM node or a ref, create a timeline, and manipulate the animation imperatively. ScrollTrigger turns that timeline into a scroll-progress binding. This is powerful when your animation isn't tied to React state — when it's tied to the user's scroll position or a sequence that spans multiple components or a scroll animation that has to survive page transitions.
The strength is precision and performance at scale. GSAP's timeline system lets you choreograph sequences with sub-millisecond timing. ScrollTrigger can pin elements, trigger animations at specific scroll positions, stagger reveals across 50+ elements, and do it all without a single layout reflow (because GSAP only touches transform and opacity). When the site is animating 20+ elements in parallel, GSAP's plugin architecture means it's optimized for exactly that load.
Now the hard truth: they both animate. If you animated a modal open with GSAP, it would work. If you scroll-triggered an animation with Framer, it would work. But "works" and "fits the tool" are different things.
Here's the decision tree we use on Velocity sites:
**Use Framer Motion if:**
- The animation is tied to React state (isOpen, isHovering, stage in a multi-step form)
- The component mounts and unmounts (modals, drawers, toasts, tooltips)
- You need AnimatePresence exit animations
- Layout animations (items rearrange, surrounding layout shifts to accommodate)
- Gesture-driven (drag, hover, tap) → Framer has gesture detection built in
**Use GSAP if:**
- The animation is tied to scroll position (enter viewport, parallax, pin-and-animate)
- The sequence spans multiple components or is stateless
- You need to orchestrate 3+ animations in tight timing
- Heavy stagger (animating 20+ child elements sequentially)
- The animation has to survive page transitions without re-triggering
**Hybrid sites use both.** The header might be a Framer Motion component with hover states and a mobile menu drawer. The hero might be all GSAP — a pinned section with a timeline that orchestrates text reveals and background shifts as the user scrolls. The footer might be another Framer component. They don't conflict because they operate on different principles: one is state-driven, the other is DOM-driven.
A common mistake is reaching for GSAP when Framer is the right choice. You end up creating refs in useEffect, managing the timeline lifecycle manually, and dealing with stale closures. The code looks longer and feels harder because you're fighting React's component model instead of working with it.
The flip mistake is using Framer for scroll animations. You can technically do it with useScroll hooks and tie animation progress to scroll, but it's fighting the library. Framer's strengths are state-driven transitions, not continuous scroll-progress binding. After 3–4 Velocity sites, you realize GSAP's scrub: true is 10 lines simpler and ships 30% smaller.
One more thing: licensing. Framer Motion is open source (MIT). GSAP's ScrollTrigger and advanced plugins require a commercial licence if your site has paid users, but for marketing sites and client projects, the standard licence is included. Read the licence before shipping. Velocity sites are mostly marketing and portfolio work, so GSAP's cost is rarely a blocker.
On Velocity, we use Framer for micro-interactions (button states, drawer open/close, form validation feedback) and GSAP for macro-sequences (hero reveals, sticky scroll sections, portfolio filtering transitions). The split is clean, the code is maintainable, and the user experience is polished because each animation is using the tool built for it.
Pick the engine that matches the animation's nature. You'll ship faster, and the code will thank you.