ReactAnimationFramer MotionUX

Framer Motion: Physics Is The New CSS 🌀

M
Matt Perry
Featured Guide 25 min read

"Your animations feel fake because nothing in the real world takes exactly 300ms to move."

In the real world, things have mass. They have friction. They have momentum.

Framer Motion ignores "duration" effectively. It uses Spring Physics. You don't say "move in 0.5s". You say "move with 300 stiffness and 20 damping". The result is UI that feels tangible, responsive, and alive.

02. Layout Magic (The "Wow" Factor)

This is Framer Motion's superpower. Add the layout prop, and the component will automatically animate to its new position when the DOM layout changes (e.g., list reordering, sorting, or when an element is removed).
It effectively snapshots the start and end layout, and FLIPs (First, Last, Invert, Play) between them efficiently.

// ❌ The Hard Way (CSS)
// Calculate positions, use transforms, hope for the best...

// ✅ The Framer Way
<motion.div layout>
  {/* I will slide to my new home automatically, no matter what happens around me */}
</motion.div>

03. Gestures & Physics

Drag, hover, tap, focus. Framer Motion handles the physics of interaction, allowing you to "throw" elements. When you release a dragged element, it doesn't just stop. It carries its momentum (velocity) and decelerates naturally using physics.

The Power of `whileHover`

CSS :hover is instant and jerky. Framer's whileHover animates to the state using your spring physics.

<motion.button
  whileHover={{ scale: 1.05 }}
  whileTap={{ scale: 0.95 }}
/>

04. Orchestration (Stagger)

Animating lists doesn't mean adding a delay to every single item `delay: index * 0.1`. Framer Motion introduces Variants to handle this elegantly. Parent variants orchestrate children.

const list = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1 // 👈 The magic line
    }
  }
}

const item = {
  hidden: { y: 20, opacity: 0 },
  show: { y: 0, opacity: 1 }
}

05. Share the Physics

"CSS transitions are dead. Long live Spring Physics. If your UI doesn't bounce, it doesn't breathe. #FramerMotion #React"

Twitter / X

The Takeaway

Your apps feel like native iOS apps. User trust increases because the interface feels physical, tangible, and responsive.

06. Physics Playground

Tweak the spring physics and interact with the boxes below. Notice how changing Stiffness and Damping alters the "feel" of the UI completely.

Feature Drag Constraints

The box below is constrained to its container. Try throwing it against the wall! Framer calculates the momentum and bounces it back.

Interactive Playground

import React, { useState } from 'react';
import { motion } from 'framer-motion';

// ----------------------------------------------------
// 🌀 PHYSICS PLAYGROUND
// ----------------------------------------------------

export default function PhysicsPlayground() {
  const [stiffness, setStiffness] = useState(300);
  const [damping, setDamping] = useState(20);

  return (
    <div className="bg-white dark:bg-[#111] text-gray-900 dark:text-gray-200 border border-gray-200 dark:border-gray-800 rounded-2xl overflow-hidden shadow-2xl p-8 flex flex-col md:flex-row gap-8 h-[600px]">
      
      {/* Controls */}
      <div className="w-full md:w-1/3 space-y-8">
          <div>
               <h3 className="text-xl font-bold mb-4">Spring Config</h3>
               
               <div className="space-y-6">
                   <div>
                       <label className="text-sm font-bold flex justify-between mb-2">
                           Stiffness <span className="text-pink-500 bg-pink-50 dark:bg-pink-900/20 px-2 rounded">{stiffness}</span>
                       </label>
                       <input 
                         type="range" min="10" max="1000" step="10" 
                         value={stiffness} 
                         onChange={(e) => setStiffness(Number(e.target.value))}
                         className="w-full accent-pink-500 h-2 bg-gray-200 dark:bg-gray-800 rounded-lg appearance-none cursor-pointer"
                       />
                       <p className="text-xs text-gray-500 mt-1">Energy. Higher = Snappier.</p>
                   </div>
                   
                    <div>
                       <label className="text-sm font-bold flex justify-between mb-2">
                           Damping <span className="text-pink-500 bg-pink-50 dark:bg-pink-900/20 px-2 rounded">{damping}</span>
                       </label>
                       <input 
                         type="range" min="0" max="100" step="1" 
                         value={damping} 
                         onChange={(e) => setDamping(Number(e.target.value))}
                         className="w-full accent-pink-500 h-2 bg-gray-200 dark:bg-gray-800 rounded-lg appearance-none cursor-pointer"
                       />
                        <p className="text-xs text-gray-500 mt-1">Friction. Lower = More Bounce.</p>
                   </div>
               </div>
          </div>

          <div className="bg-pink-50 dark:bg-pink-900/10 p-4 rounded-xl text-xs font-mono text-pink-700 dark:text-pink-300 border border-pink-100 dark:border-pink-900/30 shadow-sm">
               transition={{ "{" }} <br/>
               &nbsp;&nbsp;type: "spring",<br/>
               &nbsp;&nbsp;stiffness: {stiffness},<br/>
               &nbsp;&nbsp;damping: {damping}<br/>
               {{ "}" }}
          </div>
      </div>

      {/* Physics Area */}
      <div className="flex-1 bg-gray-50 dark:bg-black/50 border border-gray-200 dark:border-gray-800 rounded-2xl relative overflow-hidden flex items-center justify-center group">
          
           {/* Grid Pattern */}
           <div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px]"></div>
           
           {/* Draggable Box */}
           <motion.div 
              drag
              dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
              whileHover={{ scale: 1.1, cursor: 'grab' }}
              whileDrag={{ scale: 1.2, cursor: 'grabbing' }}
              whileTap={{ scale: 0.95 }}
              // The Magic:
              transition={{ type: "spring", stiffness, damping }}
              className="w-32 h-32 relative z-10 bg-gradient-to-br from-pink-500 to-purple-600 rounded-3xl shadow-2xl shadow-pink-500/30 flex items-center justify-center text-4xl select-none"
           >
               <span className="drop-shadow-md">🌀</span>
           </motion.div>

           <div className="absolute bottom-6 text-gray-400 dark:text-gray-500 text-[10px] uppercase tracking-widest pointer-events-none animate-pulse">
               Drag Me & Release
           </div>
      </div>

    </div>
  );
}