Skip to content

Motion2 min read

Making React animations feel physical, not scripted

Easing curves describe a path. Springs describe a force. Once you stop choreographing pixels and start giving them mass, interfaces begin to feel like objects instead of slideshows.

Every interface animation answers a question the user did not ask out loud: where did that thing come from, and where did it go? An easing curve answers with a schedule. A spring answers with a force. Only one of those matches how objects behave in the world your users spend the other twenty-three hours of their day in.

Why easing curves fail under the finger

A cubic-bezier is a promise about duration. The moment a user interrupts it — drags the sheet back down, taps the menu again before it has opened — that promise breaks. You get a jump, or a reversal that starts from zero velocity as if the previous motion never happened.

Springs carry velocity across interruptions for free. The sheet you flicked upward keeps its momentum when you catch it. There is no special case to write, because continuity is the default rather than a feature you bolt on.

use-sheet.ts
const { y } = useSpring({
  from: { y: 420 },
  config: { mass: 1, tension: 180, friction: 24 },
  immediate: prefersReducedMotion,
})

The three numbers that matter

Mass, tension and friction. Mass is how heavy the thing feels, tension is how eagerly it wants to arrive, friction is how quickly it stops arguing about it. I almost never touch mass — it is the one that makes motion feel sluggish rather than weighty.

If you cannot interrupt your animation halfway and have it still look right, it is choreography, not interaction.

Tuning by feel

Start from tension 180 and friction 24, then change one at a time while watching the thing at quarter speed. A spring that overshoots by two or three percent reads as alive. One that overshoots by fifteen reads as broken.

Respecting reduced motion

  • Treat prefers-reduced-motion as a first-class branch, not a media query you bolt on at the end.
  • Keep opacity transitions — motion sensitivity is about movement, not about things appearing.
  • Snap to the end state instead of shortening the duration. A fast animation is still an animation.

What I ship today

One spring configuration per product, exported from the design system, with three presets: snappy for controls, gentle for surfaces, and stiff for anything that follows a finger. Three presets cover roughly ninety percent of what an interface needs, and the remaining ten percent is where the interesting work lives.

  • Motion
  • React
  • Design Systems
  • Accessibility
All posts
  • Coming soonDesign systems

    Design tokens that survive a rebrand

    A three-layer token architecture that let us reskin an entire product in two days instead of two sprints.

  • Coming soonPerformance

    Stop shipping 400kb of JavaScript to read a blog post

    Islands, partial hydration and a hard budget. The unglamorous work that makes sites fast on real phones.

  • Coming soonAccessibility

    The modal dialog is harder than your framework admits

    Focus traps, inert, scroll locking and the five bugs every custom dialog implementation ships with.