How to Implement Interruptible Animations with CSS Transitions vs Keyframes: A Complete Guide

CSS transitions provide built-in interruptibility by retargeting from the current visual state when a property changes, while keyframe animations restart from the beginning and require JavaScript workarounds to achieve similar behavior.

Interruptible animations are essential for fluid, responsive UIs where users may trigger new states before previous animations finish—think rapidly adding toasts, toggling menus, or dragging elements. According to the emilkowalski/skills repository's design engineering standards, understanding when and how to use CSS transitions versus keyframes for interruptible animations separates polished interfaces from jarring, jumpy experiences.

Why CSS Transitions Excel at Interruptibility

The skills/emil-design-eng/SKILL.md file at line 269 establishes a clear principle: use CSS transitions over keyframes for interruptible UI. Three core mechanisms make this possible:

  • Retargetable animations — when a property changes mid-transition, the browser seamlessly continues from the element's current computed value instead of snapping back to the start
  • Hardware acceleration — transitions on transform and opacity run on the compositor thread, maintaining smooth motion even under main thread load (line 511)
  • Zero JavaScript overhead — declarative CSS reduces bundle size and automatically respects prefers-reduced-motion

This retargeting behavior is the critical difference. With keyframes, interrupting an animation typically causes a visual "jump-back-to-zero" that feels broken. With transitions, the browser calculates a new animation curve from wherever the element currently sits.

When to Use Transitions vs. Alternatives

The skills/review-animations/STANDARDS.md and related files provide a decision framework:

Situation Recommended Approach
Rapidly-triggered UI (toasts, toggles, dropdowns) CSS transition on transform/opacity
Gesture-driven motion (drag, swipe, bottom sheet) Spring-based library (Framer Motion) for velocity-aware interruptibility
Long decorative sequences (onboarding demos) CSS @keyframes acceptable—interruptibility less critical
Dynamic programmatic control (progress bars, timelines) Web Animations API (WAAPI) for precise timing with CSS performance

The skills/improve-animations/SKILL.md severity matrix flags non-interruptible dynamic UI as a high-severity issue, making this choice architecturally significant.

Implementation: Interruptible Toast with CSS Transitions

This pattern from the repository demonstrates core interruptibility:

/* toast.css */
.toast {
  opacity: 0;
  transform: translateY(100%);
  transition: opacity 400ms ease, transform 400ms ease;
}

.toast[data-visible] {
  opacity: 1;
  transform: translateY(0);
}
const toast = document.getElementById('toast');

function show() {
  toast.setAttribute('data-visible', '');
}

function hide() {
  toast.removeAttribute('data-visible');
}

// Simulate rapid toggling—transition retargets from current state
show();
setTimeout(hide, 150);   // hides mid-animation
setTimeout(show, 250);   // re-shows smoothly from partial position

Why this works: When data-visible toggles while the transition is in-flight, the browser recalculates end values and continues from the current visual state. No jump. No restart. The skills/review-animations/SKILL.md checklist explicitly validates this behavior during code review.

Modern Approach: @starting-style for Enter Animations

The repository highlights @starting-style (future-proof CSS) at line 269-274 as a cleaner alternative to JS-driven mount animations:

.modern-toast {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 400ms ease, transform 400ms ease;

  @starting-style {
    opacity: 0;
    transform: translateY(100%);
  }
}

This eliminates the common pattern of mounting-then-animating via JavaScript. The browser treats the @starting-style block as the initial state and automatically animates to the final state on element insertion.

Programmatic Control: Web Animations API (WAAPI)

When you need JavaScript control without sacrificing interruptibility, skills/emil-design-eng/SKILL.md at line 515 recommends WAAPI:

let animation;

function showToast() {
  if (animation) animation.cancel();
  
  animation = toast.animate(
    [
      { opacity: 0, transform: 'translateY(100%)' },
      { opacity: 1, transform: 'translateY(0)' }
    ],
    {
      duration: 400,
      easing: 'cubic-bezier(0.23, 1, 0.32, 1)',
      fill: 'forwards'
    }
  );
}

function hideToast() {
  if (animation) animation.cancel();
  
  animation = toast.animate(
    [
      { opacity: 1, transform: 'translateY(0)'' },
      { opacity: 0, transform: 'translateY(100%)' }
    ],
    {
      duration: 300,
      easing: 'cubic-bezier(0.23, 1, 0.32, 1)',
      fill: 'forwards'
    }
  );
}

Key implementation detail: always cancel() the existing animation before creating a new one. This prevents conflicts and maintains interruptibility. WAAPI delivers hardware-accelerated performance equivalent to CSS transitions while enabling dynamic duration calculations, timeline scrubbing, and reverse playback.

Critical Implementation Checklist

Derived from skills/review-animations/SKILL.md:

  1. Avoid transition: all — target specific properties (transform, opacity) for predictable performance
  2. Define custom easing curves — cubic-bezier values create more deliberate, "punchy" motion than defaults
  3. Leverage @starting-style — reduce JavaScript ceremony for enter animations
  4. Respect prefers-reduced-motion — disable or reduce transitions for accessibility
@media (prefers-reduced-motion: reduce) {
  .toast {
    transition: none;
    opacity: 1;
    transform: none;
  }
}
  1. Test interruptibility — rapidly toggle states in browser DevTools and confirm smooth continuation

When Keyframes Are Acceptable

CSS @keyframes remain valid for:

  • One-shot decorative animations (confetti, celebration effects)
  • Continuous ambient motion (loading spinners, pulsing indicators)
  • Complex multi-stage sequences where precise keyframe timing matters more than interruption handling

For these cases, the skills repository suggests ensuring interruptibility via JavaScript state management or simply accepting non-interruptibility when the UX context allows.

Summary

  • CSS transitions provide native interruptibility through retargeting—animations continue from current visual state rather than restarting
  • Always prefer transitions for dynamic UI (toasts, menus, toggles) per skills/emil-design-eng/SKILL.md line 269
  • Use @starting-style to eliminate JavaScript mount-animation boilerplate
  • Adopt WAAPI when programmatic control is required while maintaining CSS-level performance (line 515)
  • Avoid transition: all and always respect prefers-reduced-motion

Frequently Asked Questions

Why do my CSS keyframe animations jump when interrupted?

CSS @keyframes animations run on a fixed timeline from 0% to 100%. When interrupted and restarted, they begin from the 0% keyframe regardless of current visual state. This creates the "jump-back" effect. CSS transitions solve this by retargeting—calculating a new animation curve from wherever the element currently sits. For interruptible UI, use transitions on transform and opacity instead.

Can I make keyframe animations interruptible with JavaScript?

Yes, but it requires significant complexity. You must track animation progress via getComputedStyle, calculate the current keyframe percentage, then construct a new animation starting from that state. The skills/review-animations/STANDARDS.md recommends avoiding this approach—use transitions for interruptible UI or spring-based libraries for gesture-driven motion where velocity preservation matters.

How do I test if my animation is truly interruptible?

Rapidly toggle the triggering state in your browser. For a toast, click show/hide repeatedly with ~150ms intervals. A properly interruptible transition will smoothly reverse direction from its current position. A non-interruptible animation will snap to start points or exhibit visual glitches. The skills/review-animations/SKILL.md explicitly lists this as a required review step.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →