Tap a button. Before the animation finishes, tap it again. What happens? In a well-built interface, the second tap is respected immediately. In a naively-built one, the first animation completes first: an unresponsive moment, often described as "feeling laggy" even when every individual animation is fast. Interruptibility is the gap between motion that plays and motion that responds.
#The idea
Animations take time. During that time, users might want to do something else: tap again, drag, dismiss, change direction. A good motion system treats animation as a proposal that can be overridden, not a commitment that must complete.
Two properties matter:
- Input latency: when a user interacts during an animation, how long before the UI acknowledges the new input? Ideally zero. The old animation should cancel (or redirect) and the new one should start immediately.
- Velocity preservation: if the user's input has momentum (a drag, a flick), the animation that follows should continue with that velocity rather than starting from zero. This is why springs (lesson 4) are so much better than tweens for gestures: springs take velocity as an input.
Most CSS animations are not interruptible. A @keyframes animation played on an element will typically run to completion, and re-triggering it restarts from frame zero. That's fine for decorative motion, fatal for interactive motion. This is one of the main reasons production interaction UIs use JavaScript (Motion, spring libraries): JS animations can be canceled, redirected, or handed to the next gesture mid-flight.
Drag and release. The spring physics carry momentum. Interrupt mid-settle and the new drag picks up from wherever you grabbed.
Controls
Hover effects should feel instant, and they do, because Motion cancels in-flight animations on state change.
Controls
Tap the button repeatedly. Each tap registers immediately, even while the previous rotation is still settling.
Controls
#When it applies
- All gesture-driven motion: drag, flick, swipe, pinch. Users expect their input to be respected instantly.
- Rapid-toggle interactions: hover on/off, tap repeatedly, back-and-forth navigation.
- Anywhere a user might cancel their action: drag-to-dismiss that might be released halfway.
#When it doesn't
- System-initiated motion that shouldn't be user-interruptible: a confirmation toast shouldn't be draggable; a loading indicator shouldn't stop on tap.
- Critical sequences: error shakes, validation animations. These should play to completion so users don't miss them.
- Onboarding flows: guided moments where you want attention on a specific animation; let it play.
#Implementation
Motion's state changes cancel in-flight animations and start new ones from the current value:
// This just works:
// If the user hovers, unhovers, and hovers again quickly,
// each state change cancels the previous animation and starts fresh.
// Velocity is preserved through the motion value.
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
/>Using motion values for fully interruptible, imperative control:
const x = useMotionValue(0)
const springX = useSpring(x, { stiffness: 300, damping: 30 })
// Any update to x is smoothly tracked by springX,
// including mid-animation changes.
<motion.div style={{ x: springX }} drag="x" />useSpring wraps a motion value and applies physics. The output tracks the input smoothly, which is the basis of every velocity-preserving gesture.
#Craft notes
- Gestures should always use springs. Tweens lose velocity context on interruption; springs preserve it.
- Give users a visible cancel. Drag-to-dismiss should spring back clearly if released mid-drag. Invisible cancel feels like the system didn't register the release.
- Animate on
requestAnimationFrame, not React state, where possible. Motion values bypass React renders, which means zero dropped frames even at high input rates. - Keep input-driven state outside component state.
useMotionValue+useTransformgives you reactive animation values without triggering re-renders. This is Motion's main performance win over naive approaches.
#Exercises
- Break an animation. Find an animation in your project that doesn't interrupt well. Re-tap the trigger rapidly. Does it feel laggy? Convert it to a spring with velocity preservation and retry.
- Compare velocity handling. Build two drag demos: one that releases to a tween, one to a spring. Flick both and release. The tween ignores the flick velocity; the spring honors it.
- Study a gesture. Open iOS Control Center, drag it halfway, release, immediately grab it again. Describe what the system does and articulate why it feels coherent.
#Study this in the wild
- iOS Dynamic Island. Every interaction is interruptible. The entire thing is spring-based.
- Arc browser tab drag. Drag a tab and change direction mid-drag. Notice velocity preservation in real time.
- macOS dock magnification. Sweep the cursor back and forth across the dock rapidly. Each icon responds to current position, not the previous frame.