Objective Evaluation
Framer Motion vs CSS: When a Library Earns Its Weight
CSS covers hover, press, loops and simple entry states for free. Motion (formerly Framer Motion) adds springs, gestures, layout and exit animations. Where the line falls, with browser support.
Updated November 2026
The fundamental architectural difference
CSS is a complete animation system for interface states: transitions for hover, focus and press, keyframes for loops, @starting-style for entry, and scroll-driven timelines in current browsers. Motion, formerly Framer Motion, adds what CSS cannot express: springs that keep velocity when a gesture is interrupted, drag and pointer gestures, layout and exit animations for React components, and orchestration across many elements. Start with CSS and add Motion only for the components that need physics or coordination. A single hover does not justify the download.
Side-by-side comparison
Feature & Capability Breakdown
| Dimension | Motion (Framer Motion) | CSS animations | Advantage |
|---|---|---|---|
| Download cost | 80 KB gzip in our September 2026 test; LazyMotion can shrink the initial load | None: it ships with the browser | CSS animations |
| Hover, focus and press | whileHover, whileTap and whileFocus props | :hover, :focus-visible and :active with transition | Tie |
| Entry and exit | Animate on mount; AnimatePresence handles unmount | @starting-style for entry; an exit needs a delayed unmount | Motion (Framer Motion) |
| Interruption | Springs keep position and velocity when the target changes | A restarted transition replays its curve from the start | Motion (Framer Motion) |
| Gestures | Drag with constraints and momentum, pan, tap, focus | Hover and active states only | Motion (Framer Motion) |
| Layout and shared elements | layout and layoutId props | View Transitions API, or FLIP written by hand | Tie |
| Scroll | useScroll, useTransform and whileInView | animation-timeline in current browsers | Tie |
| Spring curves | Real springs: mass, stiffness, damping and velocity | linear() traces a fixed curve that looks like a spring | Motion (Framer Motion) |
| Reduced motion | useReducedMotion and MotionConfig | @media (prefers-reduced-motion: reduce) | Tie |
| Orchestration | Variants, stagger, delayChildren and useAnimate | Per-element delays; coordination moves into JavaScript | Motion (Framer Motion) |
When to choose CSS animations
- The state is binary: hovered, focused, pressed, open or closed.
- The animation should still work with JavaScript off, or in static HTML.
- Nothing extra belongs in the bundle, or the page is a marketing page.
- A loop, a pulse or a spinner that plays on its own.
When to choose Motion (Framer Motion)
- A gesture or a second click can interrupt the animation, and it must keep its velocity.
- A React component unmounts and its exit should animate.
- Layouts, lists or shared elements move and the change should be smooth.
- Many elements follow one sequence with stagger and shared timing.
We make ExodeUI, a different approach: states, springs and inputs live in one open .exode file that a runtime plays in React, SwiftUI, Flutter and other frameworks. For a web-only hover or fade, CSS is the better answer and we say so. How ExodeUI differs
Direct Answers