Skip to main content

Spring Physics Guide: Realistic Motion With Framer Motion

Spring animations in Framer Motion simulate real-world physics: an object attached to a spring moves toward a target position, oscillates around it, and gradually settles. By tuning three parameters—stiffness, damping, and mass—you can create motion that feels snappy, smooth, bouncy, or heavy. Understanding these parameters unlocks animations that feel alive and responsive.

I've tuned springs on modal dialogs, notification toasts, and interactive chart elements, and the difference between a poorly-tuned spring and a well-tuned one is the difference between a sluggish app and a polished one. This article covers the physics behind spring animations and practical presets for common UI patterns.

Understanding Spring Parameters

A spring animation in Framer Motion is governed by three parameters:

  • Stiffness (0–1000+): How aggressively the spring pulls the object toward the target. Higher values = faster, bouncier motion.
  • Damping (0–100+): How much the motion oscillates before settling. Higher values = less oscillation, faster settling.
  • Mass (default 1): The virtual weight of the animated object. Higher mass = slower, heavier-feeling motion; lower mass = snappier.

Together, these parameters control the spring's overshoot (how far past the target it goes), settling time (how long until it stops oscillating), and perceived weight.

The Physics Behind Spring Motion

Spring motion follows this differential equation: F = -k × x - c × v, where:

  • k is stiffness (spring constant)
  • x is displacement from the target
  • c is damping coefficient
  • v is velocity

Framer Motion solves this equation each frame, calculating the new position and velocity. When stiffness is high and damping is low, the object overshoots the target, then oscillates back. High damping prevents overshoot, settling the motion quickly (Framer Motion, 2026).

Tuning Spring Animations: Stiffness vs Damping

Let's compare how different stiffness and damping values feel:

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

export function SpringComparison() {
const [trigger, setTrigger] = React.useState(false);

const springs = [
{
name: 'Stiff (snappy)',
config: { stiffness: 300, damping: 30 }
},
{
name: 'Molasses (smooth)',
config: { stiffness: 30, damping: 20 }
},
{
name: 'Bouncy (elastic)',
config: { stiffness: 200, damping: 10 }
},
{
name: 'Heavy (weighted)',
config: { stiffness: 100, damping: 20, mass: 2 }
}
];

return (
<div>
<button onClick={() => setTrigger(!trigger)}>
Trigger All Springs
</button>

<div style={{ display: 'grid', gap: '20px', marginTop: '20px' }}>
{springs.map((spring) => (
<div key={spring.name}>
<p>{spring.name}</p>
<motion.div
animate={{
x: trigger ? 200 : 0
}}
transition={{
type: 'spring',
...spring.config
}}
style={{
width: '60px',
height: '60px',
backgroundColor: '#3498db',
borderRadius: '4px'
}}
/>
</div>
))}
</div>
</div>
);
}

Click the button and watch how each spring behaves:

  • Stiff: Fast response, minimal overshoot, settles immediately.
  • Molasses: Slow, deliberate motion, very smooth, no overshoot.
  • Bouncy: Fast but overshoots significantly, bounces 2–3 times before settling.
  • Heavy: Slower start due to mass, settles smoothly with slight bounce.

The choice depends on context: a button press should feel snappy (stiff), while a draggable element being released can feel bouncy (elastic).

Spring Presets for Common Patterns

Framer Motion doesn't include built-in presets, but here are recommended configurations for typical use cases:

Microinteraction Spring (Button Feedback)

transition={{
type: 'spring',
stiffness: 400,
damping: 40
}}

Fast response (< 0.2s settle time), minimal overshoot. Best for buttons, icon toggles, and micro-feedback animations. The user feels immediate response.

Standard Interactive Spring (Hover, Tap)

transition={{
type: 'spring',
stiffness: 260,
damping: 20
}}

Balanced feel: responds quickly but with a subtle bounce. Appropriate for card reveals, menu items, and general interactive components. Feels responsive without being hyperactive.

Smooth Entrance Spring (Modal, Sidebar)

transition={{
type: 'spring',
stiffness: 100,
damping: 15
}}

Slower, more graceful motion. Used for larger components entering the viewport or overlays appearing. The longer duration (0.5–0.7s) feels luxurious.

Elastic Spring (Playful UI, Drag Release)

transition={{
type: 'spring',
stiffness: 150,
damping: 12,
mass: 0.5
}}

Noticeable bounce and overshoot (10–20%). Used for character animations, game-like elements, or drag-and-drop release. Feels playful and energetic.

Heavy Spring (Weighted, Serious Motion)

transition={{
type: 'spring',
stiffness: 80,
damping: 25,
mass: 2
}}

Slow, deliberate motion with a sense of weight. Used for dragging heavy objects, loading states, or serious/business applications. Conveys solidity and substance.

Damping Ratio: The Technical Approach

Spring behavior is characterized by the damping ratio, which determines how the spring oscillates:

  • Underdamped (< 1.0): Oscillates past the target (bouncy).
  • Critically damped (= 1.0): Reaches the target without overshooting.
  • Overdamped (> 1.0): Approaches the target slowly without overshooting.

The damping ratio is calculated from stiffness, damping, and mass. For most UI animations, aim for a damping ratio of 0.5–1.0 (slightly underdamped to critically damped). If an animation overshoots too much, increase damping or decrease stiffness.

Combining Spring with Other Animations

Use springs for primary interactive feedback and tweens for secondary animations:

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

export function FormSubmit() {
const [submitted, setSubmitted] = React.useState(false);

return (
<motion.form
onSubmit={(e) => {
e.preventDefault();
setSubmitted(true);
}}
>
<input type="text" placeholder="Name" />

<motion.button
animate={{
scale: submitted ? 1.1 : 1,
backgroundColor: submitted ? '#27ae60' : '#3498db'
}}
transition={{
scale: { type: 'spring', stiffness: 300, damping: 20 },
backgroundColor: { duration: 0.3 }
}}
type="submit"
style={{
padding: '10px 20px',
borderRadius: '4px',
border: 'none',
color: '#fff',
cursor: 'pointer'
}}
>
{submitted ? 'Sent!' : 'Submit'}
</motion.button>
</motion.form>
);
}

The button's scale uses a spring (responsive, playful), while backgroundColor uses a tween (precise color transition). Each animation type serves its purpose.

Testing and Fine-Tuning

Start with a preset configuration and adjust incrementally:

  1. Record the animation with DevTools Performance.
  2. Check the settling time (when oscillation stops < 1px).
  3. If it settles too slowly, increase damping or stiffness.
  4. If it overshoots too much, increase damping.
  5. If it feels too slow, decrease mass or increase stiffness.

Real-world spring tuning is iterative. Test on actual devices to ensure the motion feels right at 60fps.

Key Takeaways

  • Springs simulate physics: stiffness controls pull strength, damping controls oscillation, mass controls heaviness.
  • Stiff springs (300+ stiffness, 30+ damping) feel snappy and responsive for micro-interactions.
  • Smooth springs (80–150 stiffness, 15–25 damping) feel luxurious for larger entrance animations.
  • Combine springs with tweens for different animations within one component (spring for interactive, tween for transitions).
  • Fine-tune by recording performance and adjusting stiffness, damping, and mass iteratively.

Frequently Asked Questions

What's the difference between stiffness and mass?

Stiffness controls how hard the spring pulls; mass controls the inertia. Higher stiffness makes the spring pull harder (faster motion). Higher mass makes the object feel heavier, resisting acceleration (slower motion). Increase stiffness for responsive feel; increase mass for weighty, serious motion.

Why does my spring animation look janky on slower devices?

Spring animations with very high stiffness and low damping require many frame updates to calculate smoothly. On slower devices (< 60fps), the animation skips frames. Use moderate settings (stiffness < 300, damping > 20) and test on target devices. Falling back to tween animations on low-end devices is a valid optimization.

How do I calculate damping ratio from my parameters?

The precise formula depends on Framer Motion's internal solver. Empirically, aiming for a damping ratio of 0.6–0.9 works well: increase damping or decrease stiffness if the spring overshoots too much.

Can I animate multiple properties with different spring configurations?

Yes. Use the transition prop as an object with per-property settings: transition={{ x: { type: 'spring', stiffness: 300 }, y: { duration: 0.5 } }}.

Further Reading