Back to work
interactive//3 min read

Spring Physics for UI

Why linear easing feels dead, how springs bring interfaces to life, and interactive demos you can touch.


Every animation you see on a well-crafted interface is driven by physics — not by timelines. The difference between an interface that feels alive and one that feels like a slideshow comes down to one primitive: the spring.

Why springs?

CSS ease-in-out follows a fixed bezier curve. It always takes the same path, the same duration. It cannot overshoot. It cannot respond to interruption. It is mathematically incapable of feeling natural.

A spring, by contrast, is defined by physical properties — stiffness, damping, and mass. Change any one of them and the character of the motion transforms completely.

Feel the difference

These three boxes use the exact same whileHover scale, but different spring configurations. Hover and tap each one — notice how the personality changes:

Bouncy
Snappy
Stiff
Same scale transform, different spring configs. Hover and tap each.

The bouncy spring (low damping) overshoots and oscillates before settling. The snappy spring reaches its target quickly with a subtle overshoot. The stiff spring is nearly instant, like a physical button.

The two parameters that matter

In practice, you only need to think about two values:

  • Stiffness — how fast the spring pulls toward its target. Higher = faster.
  • Damping — how quickly oscillation dies out. Higher = less bounce.
transition={{
  type: "spring",
  stiffness: 400,  // fast pull
  damping: 17,     // moderate bounce
}}

There's a sweet spot for UI interactions: stiffness: 300–500, damping: 15–25. This range covers most hover effects, button presses, and layout shifts.

Variant propagation

One of the most powerful patterns in Motion is variant propagation — a parent's hover state automatically cascades to children with staggered timing. This creates orchestrated motion without managing individual animations:

const container = {
  rest: {},
  hover: {
    transition: {
      staggerChildren: 0.05,
    },
  },
};

const dot = {
  rest: { y: 0, opacity: 0.4 },
  hover: { y: -8, opacity: 1 },
};

<motion.div
  variants={container}
  initial="rest"
  whileHover="hover"
>
  {items.map((i) => (
    <motion.div
      key={i}
      variants={dot}
      transition={{
        type: "spring",
        stiffness: 400,
        damping: 15,
      }}
    />
  ))}
</motion.div>
Hover me
Parent hover drives staggered children. No manual orchestration needed.

The parent defines whileHover="hover" and its staggerChildren timing. Each child only defines what hover looks like. Motion handles the cascade.

Springs in 3D space

Springs aren't limited to scale and position. Apply them to rotation and you get responsive 3D interactions. This card uses useSpring to smoothly follow your pointer with perspective tilt:

Tilt CardMove your pointer across this card
Move your pointer across the card. Springs smooth the 3D rotation.

The key insight: we never set rotation directly. We set a target, and the spring interpolates there naturally. If the pointer moves fast, the card lags slightly. If the pointer leaves, it settles back to zero. All from two useSpring values.

const rotateX = useSpring(0, { stiffness: 200, damping: 20 });
const rotateY = useSpring(0, { stiffness: 200, damping: 20 });

// On pointer move, just set the target:
rotateX.set(tiltValue);

Following the cursor

Take the spring concept further: instead of rotating, translate an element to follow the pointer entirely. Low damping creates a trailing, elastic feel:

Move pointer inside
The circle follows your pointer with spring physics. Notice the elastic delay.

The lower the damping and stiffness, the more the element trails behind the pointer. This is the same principle behind cursor followers, magnetic buttons, and parallax effects.

The principle

Springs replace the question "how long should this take?" with "how should this feel?" Duration becomes emergent from physics rather than declared upfront. This is why spring-driven interfaces feel responsive even under load — the animation adapts to its current velocity instead of replaying a fixed timeline.

Start with stiffness: 400, damping: 17 for most interactions. Tune from there.