Interrupted and Reversing Transitions: What Happens Mid-Flight

Users do not wait for animations to finish. They sweep the pointer across a row of cards, tap a toggle twice in quick succession, press Escape half a second after opening a menu. Every one of those actions interrupts a transition that is still running, and how the browser handles the interruption determines whether the interface feels responsive or glitchy. CSS transitions handle interruption remarkably well by default — they reverse from wherever they are, and they even shorten the reverse to match how far they got — but only if you understand and preserve the conditions that make that work. This page explains the rules and the common ways they are accidentally broken. It belongs to CSS Transition Fundamentals in the CSS-Only Micro-Interactions & Animations guide.

Why interruption matters

A transition that runs for 300 milliseconds is interrupted whenever the user's next action comes within those 300 milliseconds — which, for hover effects on dense interfaces, is most of the time. If interruption produced a jump or a restart, hover effects on a list would flicker constantly as the pointer moved across items. The quality of interrupted motion is therefore not an edge case; it is the ordinary case for many micro-interactions.

Two things can go right or wrong. The starting point of the new motion should be the element's current visual state, not either endpoint. And the duration of the new motion should match the distance left to travel, not the full original duration.

Three ways an interruption could behave A 400 millisecond transition is interrupted after 100 milliseconds at about a quarter of its travel. CSS transitions reverse from that point back to the start in about 100 milliseconds. A naive implementation would jump to the end first, or restart from zero with the full duration. Interrupted at 100ms of a 400ms hover time hovered rest pointer leaves CSS: reverse from here, ~100ms jump, then full reverse

The complete implementation

The demo compares a transition-based hover with a keyframe-based one. Sweep the pointer quickly across both rows and back.

Live demoTransition versus keyframes when a hover is interrupted
Sweep the pointer quickly across both rows: the transition chips sink back from wherever they reached; the keyframe chips snap to the floor.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Interrupted transitions</title>
<style>
  body { font: 15px/1.5 system-ui, sans-serif; margin: 1.5rem; display: grid; gap: 1.5rem; }
  .row { display: flex; gap: 0.5rem; }
  .chip {
    padding: 0.8rem 1rem;
    border-radius: 8px;
    background: #e0e7ff;
    color: #1e1b4b;
  }

  /* TRANSITION: interrupting reverses smoothly from the current value,
     and a reverse after a short hover is correspondingly short. */
  .row--transition .chip {
    transition: translate 400ms cubic-bezier(0.22, 1, 0.36, 1),
                background-color 400ms ease;
  }
  .row--transition .chip:hover {
    translate: 0 -10px;
    background-color: #a5b4fc;
  }

  /* KEYFRAMES: when :hover stops matching, the animation is removed and
     the chip snaps back instantly, wherever it was. */
  .row--keyframes .chip:hover {
    animation: lift 400ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
  }
  @keyframes lift {
    to { translate: 0 -10px; background-color: #a5b4fc; }
  }

  @media (prefers-reduced-motion: reduce) {
    .row--transition .chip { transition-duration: 1ms; }
    .row--keyframes .chip:hover { animation: none; background-color: #a5b4fc; }
  }
</style>
</head>
<body>
  <div class="row row--transition"><span class="chip">Transition</span><span class="chip">Transition</span><span class="chip">Transition</span></div>
  <div class="row row--keyframes"><span class="chip">Keyframes</span><span class="chip">Keyframes</span><span class="chip">Keyframes</span></div>
</body>
</html>

The transition row feels fluid under fast pointer movement: each chip rises only as far as it got while hovered and sinks back from that point. The keyframe row stutters: every chip that the pointer leaves mid-lift snaps to the floor, because removing :hover removes the animation entirely. That single difference is why transitions are the right tool for reversible state changes, and keyframes for one-way or looping effects, a distinction developed further in Keyframe Animation Patterns.

The key technique: reversal shortening

Starting from the current value solves the jump. It does not, on its own, solve the timing: a 400-millisecond reverse from a point only 10% along the path would crawl back over 400 milliseconds, feeling sluggish. The transition model handles this with reversal shortening. When a new transition returns a property to the value it was transitioning from — a reversal — the browser scales the new duration by how far the interrupted transition had progressed.

Reverse duration follows progress An interruption at 25 percent progress reverses in about 100 milliseconds, at 50 percent in about 200 milliseconds, and at 90 percent in about 360 milliseconds. The reverse always takes roughly as long as the forward motion had run. 400ms transition, reversed at different points at 25% reverse ≈ 100ms at 50% reverse ≈ 200ms at 90% ≈ 360ms A brief hover produces a brief return, never a slow crawl back.

The shortening is proportional to output progress, which accounts for easing: with a strong ease-out, 100 milliseconds may already be 40% of the visible travel, and the reverse is scaled accordingly. The practical upshot is that you can choose a comfortable forward duration without worrying that quick interactions will feel slow on the way back.

Shortening only applies to a true reversal — returning to the previous start value. If a third state intervenes (a hover, then a click that sets a different value), the new transition runs for its full declared duration from the current value to the new target, which is also correct: it is a new journey, not a return.

How interruption interacts with per-state timing

Because the transition values of the entered state apply, an interruption can switch curves mid-flight. A hover with a slow, bouncy entry and a quick, plain exit — the asymmetry recommended in Entry vs Exit Easing — reverses with the exit curve from the current point when interrupted. That is usually exactly what you want. Two edge cases deserve awareness:

  • Overshooting curves. If the entry curve overshoots and the pointer leaves at the peak, the reverse starts from beyond the resting position. That looks natural for springs and odd for large overshoots.
  • Delays. A transition-delay on the entered state applies to the interruption too. A hover-intent delay on the open state means a quick re-entry waits again before resuming — correct for menus, surprising for simple hover lifts.

Things that break interruption

Transitions interrupt cleanly only while the browser can see a continuous change in one property's computed value. Several common patterns quietly break that.

Changing transition on the fly with a class that also removes the property. If a script swaps a class that sets both the target value and transition: none — often done to "reset" an element before animating it — the interruption becomes a jump. Separate the reset from the state change, or use @starting-style for first-frame values.

Animating between different value types. A transition from height: 0 to height: auto has no interpolable path without interpolate-size, so an interruption jumps. The same applies to display, background-image and other discrete properties unless allow-discrete or a registered custom property provides a path.

Replacing the element. Frameworks that re-render a list by replacing DOM nodes destroy the old element mid-transition and insert a new one at its resting state. Keying list items stably so the same node persists is what lets its transition continue.

Mixing transitions and animations on one property. A running keyframe animation overrides the transitioned value for as long as it runs. If both target transform, the transition's interruption behaviour is invisible while the animation plays and snaps when it ends. Give each mechanism its own property — translate for the transition, rotate for the animation — so they compose instead of competing.

Very long durations. Interruption and shortening work at any duration, but a 1.5-second transition interrupted frequently still feels laggy, because every short hover produces motion that outlasts the user's attention. Micro-interactions should stay under about 300 milliseconds, as argued in Choosing Animation Durations.

Variation: making a keyframe animation interruptible

Sometimes a keyframe animation genuinely is the right tool — for a multi-step effect that a transition cannot express — but it must still reverse gracefully. One approach keeps the animation applied and pauses or reverses it rather than removing it.

.badge {
  animation: pop 500ms cubic-bezier(0.34, 1.56, 0.64, 1) both paused;
}

/* Run forward while hovered; hold wherever it is when not. */
.badge:hover {
  animation-play-state: running;
}

This holds the effect in place instead of snapping when the pointer leaves, but it cannot run backwards on un-hover. For true reversal of a keyframe effect, the Web Animations API's animation.reverse() is needed, one of the cases covered in When to Use JavaScript Animation. For most micro-interactions, rewriting the effect as a transition — possibly on several properties with different delays — is simpler and interrupts correctly for free.

Browser support

CSS transitions, including starting from the current value on interruption and reversal shortening, behave consistently in all current browsers; the transition shorthand dates from Chrome 26, Edge 12, Firefox 16 and Safari 9. The standalone translate property is supported in Chrome and Edge 104+, Firefox 72+ and Safari 14.1+. animation-play-state is supported in Chrome 43+, Edge 12+, Firefox 16+ and Safari 9+.

FAQ

What happens if the user un-hovers halfway through a hover transition? The browser starts a new transition from the element's current, partly transitioned value back toward the original value. It does not jump to the end or restart from the beginning, so the element reverses smoothly from where it was.

What is reversal shortening? When a transition is reversed before it finishes, the browser shortens the reverse transition in proportion to how far the original had progressed. A 400ms transition interrupted after 100ms reverses in about 100ms rather than a full 400ms, so quick hovers produce quick returns.

Why do my keyframe animations jump when interrupted? Removing the class or state that applies an animation removes the animation entirely, so the element snaps to its unanimated style. Keyframe animations have no built-in reverse from the current point. Use transitions for state changes that can be interrupted.

Can different curves apply to each direction of an interrupted transition? Yes. The transition values of the state being entered apply. If the base state and the hover state declare different curves and durations, an interruption switches to the curve of whichever state is now being entered, starting from the current value.

Related articles

More pages in the same section.