The Loading Spinner That Wouldn’t Spin in San José
It was 11 p.m. in a Costa Rican co‑working loft. Daniela, a junior dev tuning in from Warsaw, DM’d me: “The spinner stalls the whole React page.” She’d wired a vanilla CSS keyframe that clashed with our Tailwind purge config. Ten minutes of frantic pair‑debugging later, we swapped the spinner to Framer Motion—one line of props, buttery smooth. The next day our design lead asked how the same trick would look in React Spring. That conversation morphed into this guide: a practical, side‑by‑side comparison of the two animation heavyweights powering most modern React apps.
Why Motion Libraries Still Matter
Attention spans keep shrinking; micro‑interactions guide users and surface state changes. React 19’s server components defer hydration, so client‑side animation must feel instantaneous. Framer Motion 12.23.6 introduces automatic reduced‑motion fallbacks and scroll‑linked timelines Yarn, while React Spring 10.0.1 refactored its physics core for 30 % smaller bundles npm. Knowing when to reach for each library saves kilobytes, dev hours, and remote debug sessions.
Quick‑Reference Toolbelt
Tool / Concept | One‑liner purpose |
---|---|
Framer Motion 12 | Declarative prop‑based animation with variants & layout. |
React Spring 10 | Physics‑driven hooks for fluid, interruptible motion. |
Motion One | Tiny Web‑Animations‑API wrapper (2 kB) for small widgets. |
Lottie React | JSON‑driven vector animations exported from After Effects. |
CLI Command | What it does |
---|---|
npm i framer-motion | Installs Motion 12.23.x. |
npm i @react-spring/web | Installs React Spring 10 core. |
npm i motion one | Alternative micro‑library. |
npm i eslint-plugin-jsx-a11y | Lints for reduced‑motion, tab order, etc. |
Concept Primer — Plain English
- Declarative vs. Physics: Motion’s prop syntax (
<motion.div animate={{x:100}} />
) feels like CSS‑in‑JS; Spring relies on hooks returning animated values (useSpring
). - Variants: Motion groups multiple states (hover, tap) under one key.
- Layout Animations: Motion automatically animates position change; Spring needs manual measurement.
- Shared Layout: Motion’s
layoutId
morphs one component into another (cards → modal). - Gesture Hooks: Spring integrates with
@use-gesture/react
for drag‑and‑drop inertia.
Step‑by‑Step Walkthroughs
1 — Fade & Slide In: Framer Motion
tsxCopyEditimport { motion } from 'framer-motion';
export const Card = ({ children }: { children: React.ReactNode }) => (
<motion.article
initial={{ opacity: 0, y: 40 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, ease: 'easeOut' }}
className="p-4 rounded bg-white shadow"
>
{children}
</motion.article>
);
Line‑by‑line
initial
andanimate
props declare start/end.- Motion auto‑registers
opacity
&transform
withwill-change
. easeOut
comes from Material‑like cubic‑bezier presets.
2 — Physics Bounce: React Spring
tsxCopyEditimport { useSpring, animated } from '@react-spring/web';
export const BouncyCard = ({ children }: { children: React.ReactNode }) => {
const styles = useSpring({
from: { opacity: 0, y: 40 },
to: { opacity: 1, y: 0 },
config: { tension: 170, friction: 20 },
});
return (
<animated.article style={styles} className="p-4 rounded bg-white shadow">
{children}
</animated.article>
);
};
Highlights
- Springs continue past the target (overshoot) and settle—you control via
tension
&friction
. - Spring values are interruptible; call
api.start({ ... })
within events.
3 — Hover & Tap Variants (Motion)
tsxCopyEdit<motion.button
whileHover="hover"
whileTap="tap"
variants={{
initial: { scale: 1 },
hover: { scale: 1.05 },
tap: { scale: 0.95 },
}}
/>
No state hooks; just declarative variant names.
4 — Drag with Momentum (Spring)
tsxCopyEditimport { useSpring, animated } from '@react-spring/web';
import { useDrag } from '@use-gesture/react';
export const Draggable = () => {
const [{ x, y }, api] = useSpring(() => ({ x: 0, y: 0 }));
const bind = useDrag(({ offset: [ox, oy] }) => api.start({ x: ox, y: oy }));
return <animated.div {...bind()} style={{ x, y }} className="w-32 h-32 bg-blue-500 rounded" />;
};
Gesture + spring delivers native‑feeling throw & settle.
Feature Matrix
Feature | Framer Motion 12 | React Spring 10 |
---|---|---|
Bundle (gzip) | ~55 kB | ~20 kB |
Variants / State | ✔ Built‑in | ❌ Use multiple springs |
Shared Layout | ✔ layoutId | 🚧 Manual measure + springs |
SVG Morph | ✔ pathLength | ✔ useTrail over path points |
3D / Canvas | Via Framer Motion 3D | Via @react-spring/three |
Gesture | Basic drag; inertial drag plugin | Deep via @use-gesture |
Reduced Motion Auto‑respect | ✔ Prefers‑reduced‑motion CSS fallback | ☐ manual check |
Common Pitfalls & Fixes
Pitfall | Symptom | Fix |
---|---|---|
Re‑created springs on every render | Perf stutter | Wrap configs in useMemo ; store Motion variants outside component. |
Hard‑coded durations | Prefers‑reduced‑motion users get nausea | Gate with window.matchMedia('(prefers-reduced-motion)') . |
Layout shift on exit | Component unmounts instantly | Motion’s AnimatePresence or Spring’s useTransition preserve node until animation completes. |
Oversized bundle | Framer + Spring both installed | Standardize on one; tree‑shake unused features with import { motion } from 'framer-motion/dom' . |
Remote‑Work Insight Box
Our cross‑time‑zone rule: every animation PR must include a 10 second Loom video. Storybook’s Interaction tab can record these automatically. Colleagues in Panama comment while I’m asleep in Brazil, so by dawn the spring tension is dialed without a single synchronous meeting.
Performance & A11y Checkpoints
- DevTools Performance: Watch for long purple layout bars—shared‑layout transitions can thrash if
position: absolute
mis‑matches. - INP Score: Spring’s physics loops may hog main thread; use
config: { clamp: true }
to end quickly on low‑end phones. - Lighthouse: Confirm reduced‑motion attr; Motion auto‑disables but Spring requires a custom flag.
- GPU Layers: Animating
transform
andopacity
only; avoidtop/left
. Motion locks this by design; Spring trusts you. - SSR Flash: Motion’s
LazyMotion
defers library code until first animation, slicing ~30 kB from first payload.
Optional Diagram Description
SVG: Two lanes—Declarative (Motion) and Physics (Spring). Each shows data flow: Props → Internal Scheduler → DOM vs. Hooks → Spring Engine → DOM. Arrows highlight where gestures feed Spring and where variants feed Motion.
Wrap‑Up — Choosing Your Animation Tool
- Reach for Framer Motion when you need timeline‑like sequences, shared‑layout magic, or designer‑friendly variant syntax.
- Use React Spring for physics‑heavy interactions, gesture accuracy, or Three.js scenes.
- Optimize for bundle size: Motion’s
LazyMotion
or Spring’s selective sub‑packages. - Always respect reduced‑motion and test with DevTools throttling to mimic real devices.
Mastering these libraries turns static UIs into delightful experiences—without late‑night color‑contrast disasters. Which library will power your next project? Share your thoughts below; I’ll reply after my next remote stand‑up—maybe from a café in the Dominican Republic.