# How to Make UI Animation Interruptible in React

> An animation is interruptible when a new user input redirects it from wherever it currently is, keeping its current velocity, instead of restarting from a fixed start value. Springs are interruptible because they describe a target and a physical state; duration-based transitions are not, because they replay a fixed curve from the start. In React, the fix is to drive the value with a spring and read the current value and velocity when the next input arrives.

Updated October 2026. Source: https://www.exodeui.com/learn/interruptible-animation-react/

## What actually goes wrong

A CSS transition from A to B has no memory. If the user reverses direction halfway, the element snaps toward A from a curve that assumes it started at rest. The result is a visible jump in speed, and sometimes a visible jump in position if the property is being restarted by a class toggle. The user reads this as the interface ignoring them.

- Duration-based: fixed start, fixed end, fixed time. Interruption restarts the clock.
- Spring-based: a target plus current position and velocity. Interruption only changes the target.
- The tell is a speed change at the moment of input. Position rarely jumps in springs; it jumps constantly in restarted transitions.

## The three inputs that get interrupted most

Interruption shows up in predictable places. Fixing these three covers most of the visible jank in a product.

- Drag then release: the element must continue with the throw velocity, not stop dead at the release point.
- Toggle tapped twice quickly: the second tap must reverse direction from the current position.
- Sheet or drawer dismissed mid-open: the close must start from the current offset, not from fully open.

## The spring model in one paragraph

A spring is described by mass, stiffness and damping. Stiffness pulls toward the target, damping removes energy, mass resists both. When you change the target mid-flight, you keep the current position and velocity and let those three numbers do the rest. That is the whole trick, and it is why spring libraries feel better than transitions under fast input.

- Higher stiffness: faster response, more chance of overshoot.
- Higher damping: less overshoot, slower settle.
- Critically damped: no overshoot. Use it for menus and sheets where a bounce looks accidental.
- Underdamped: small overshoot. Use it for playful, low-stakes controls.

## Implementing it in React

With Motion, use a spring transition and let the component re-render with a new target; Motion keeps the in-flight velocity. With React Spring, useSpring does the same through its animated values. With plain React state you cannot do this at all, because state changes are discrete: by the time you read the new value the old animation is already gone.

- Pass the target as state, never as a fixed duration.
- Do not remount the element on interrupt. Remounting throws away the velocity you need.
- Keep the spring configuration in one place so every interruptible element feels related.

## When a state machine file is the better home

If the interruptible behaviour has to be identical on web and in native apps, or a designer needs to tune the spring without shipping code, the spring belongs in the file rather than the component. ExodeUI stores mass, stiffness and damping per animation, so the same curve plays in React, SwiftUI and Flutter, and React only changes the input that starts or reverses it.

- Keep the transition rule in the file, not in a React effect that fires on every render.
- Expose one input per direction rather than a single "open" boolean if reverse needs to keep velocity.
- Read input names from the file before wiring the React side.

## How to test that it is actually interruptible

Manual clicking hides the problem because people rarely click faster than the animation. Test it deliberately.

- Click or drag twice within 100 ms and watch for a speed discontinuity at the second input.
- Record the interaction and step through frames; a restart shows as a velocity spike.
- In Playwright, drive two pointer events with a short gap and compare screenshots at fixed offsets.
- Check reduced motion: the element should jump directly to the target with no spring at all.

## Frequently asked questions

### What does interruptible animation mean?

It means a new user input can redirect the animation from its current position and velocity, instead of restarting it from a fixed starting point. Springs are interruptible by design; CSS transitions and fixed-duration tweens are not.

### Why does my CSS transition jump when I click twice quickly?

A transition has no memory of the previous run. Toggling the class restarts the curve from the beginning, so the element briefly moves at a speed that does not match where it actually is. A spring keeps position and velocity across the change and only swaps the target.

### Do I need a library for interruptible animation in React?

For spring behaviour, yes, or you write the integration loop yourself. React state alone cannot express velocity because updates are discrete. Motion and React Spring both keep the in-flight value for you.

### How do I test that an animation is interruptible?

Trigger the reverse input within about 100 ms of the first, and watch for a speed change at the moment of input. Repeat with pointer events in Playwright and compare frames at fixed offsets. A restart shows as a velocity spike.

### What spring settings should I start with?

Stiffness around 400, damping around 38 and mass near 1 gives a quick settle with almost no overshoot, which suits sheets and menus. Lower damping to about 26 for a small, deliberate overshoot on playful controls.

## Related

- [Spring animation fundamentals](https://www.exodeui.com/learn/spring-animation)
- [React and ExodeUI inputs](https://www.exodeui.com/learn/react-exodeui-state-machine-inputs)
- [Best spring animation tools](https://www.exodeui.com/best-spring-animation-tools)
- [Testing animations with Playwright](https://www.exodeui.com/learn/testing-animations-playwright)
