design hubs
On this page
Choreography
4 min read

Spring Physics

Spring animations overshoot their target and oscillate before settling, producing motion that feels genuinely physical in a way cubic-bezier curves cannot replicate.

A cubic-bezier curve always reaches its destination exactly on time. A spring overshoots, bounces back, and settles. The difference is the difference between a machine and a physical object.

How spring physics work

A spring animation is defined not by a duration and a curve, but by physical parameters: stiffness (how strong the spring is — higher stiffness means faster, snappier motion), damping (how quickly the oscillation dies out — higher damping settles faster with less overshoot), and mass (how heavy the object is — higher mass produces slower, more ponderous motion). The combination of these three values produces a curve that is not constrained to a fixed duration — it runs until the oscillation settles below a threshold.

Why it feels different

Cubic-bezier easing is a mathematical interpolation between two values over a fixed time. It is predictable and controllable, but its relationship to physics is only metaphorical. Spring physics is an actual simulation — the curve emerges from the physical properties of the system rather than being specified directly. This is why spring-animated elements feel like they have mass: because within the simulation, they do.

When to use springs

Springs are most appropriate for interactive, physical-feeling elements: a draggable component that snaps to a grid, a sheet that is pulled up and released, a button that bounces on press. They work best when the user’s interaction has a physical quality — pulling, throwing, pressing — and the spring’s response reinforces that quality. Springs feel wrong for informational animations — a tooltip appearing, a notification arriving — where the mechanical precision of a cubic-bezier is more appropriate.

CSS and library implementation

CSS cubic-bezier() cannot simulate spring overshoot — it is constrained to values between 0 and 1. The CSS linear() function (with many keyframe stops approximating a spring curve) can simulate springs in CSS, but the values must be pre-calculated. Most implementations use JavaScript animation libraries — Framer Motion, React Spring, Motion One — which provide first-class spring primitives. When using a library, prefer the spring API over pre-calculated keyframes so the physics parameters remain editable.

Tuning springs

The most common mistake with springs is excess bounce — an element that oscillates visibly before settling reads as playful or cartoon-like rather than physical. For most UI elements, aim for near-critical damping: a slight overshoot (5–10% past the target) before settling quickly. Reserve bouncier springs for explicitly playful contexts — games, onboarding, celebration moments — and use stiffer, more damped springs for professional application interfaces.

Gesture-driven animation

Gesture-driven animation is motion that tracks user input in real-time, rather than playing a pre-defined curve on a discrete event. The element’s position, scale, or opacity is directly tied to the user’s pointer or touch position — the animation is the gesture.

Common patterns:

  • Swipe to dismiss — a list item follows the horizontal drag. If the drag reaches a threshold, a spring snaps it off-screen; otherwise, a spring snaps it back.
  • Pull to refresh — a feed follows the vertical overscroll with increasing resistance. Release triggers a loading state or a spring snap back.
  • Drag to reorder — an item follows the pointer while others shift to fill the gap. Release triggers a spring settle to the new position.

The critical distinction from click-triggered animation: the motion is continuous and bidirectional. The user can reverse direction mid-gesture and the animation must follow. Spring physics is essential here — a cubic-bezier curve with a fixed endpoint cannot follow a gesture that may reverse, pause, or release at any velocity.

Implementation uses pointer events to track position and directly sets transform values, then hands off to a spring animation on release with the pointer’s final velocity as the spring’s initial velocity:

element.addEventListener('pointerup', () => {
  const velocity = getCurrentVelocity(); // px/ms at release
  springAnimate({ to: 0, initialVelocity: velocity, stiffness: 200, damping: 25 });
});

Accessibility: every gesture-driven interaction must have a non-gesture alternative. Swipe-to-dismiss needs a delete button. Drag-to-reorder needs keyboard reordering. WCAG 2.5.1 requires that all path-based gesture functionality is also operable by single-pointer actions.

The takeaway

Use spring physics for interactions that have a physical quality — dragging, releasing, pressing. Keep damping high for most interfaces (minimal bounce). Use cubic-bezier for informational and navigational animations. Never reach for a spring just because it looks more interesting — match the physical quality of the animation to the physical quality of the interaction.

Practice

0 / 3

Keyboard shortcuts

Show shortcuts
?
Search
CtrlK
Previous article
Next article
Close
Esc