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

> Learn how to implement interruptible CSS animations using transitions vs keyframes. Discover which method offers built-in interruptibility and when to use JavaScript workarounds.

- Repository: [Emil Kowalski/skills](https://github.com/emilkowalski/skills)
- Tags: how-to-guide
- Published: 2026-08-05

---

**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`](https://github.com/emilkowalski/skills/blob/main/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`](https://github.com/emilkowalski/skills/blob/main/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`](https://github.com/emilkowalski/skills/blob/main/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:

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

.toast[data-visible] {
  opacity: 1;
  transform: translateY(0);
}

```

```javascript
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`](https://github.com/emilkowalski/skills/blob/main/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:

```css
.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`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) at line 515 recommends WAAPI:

```javascript
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`](https://github.com/emilkowalski/skills/blob/main/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

```css
@media (prefers-reduced-motion: reduce) {
  .toast {
    transition: none;
    opacity: 1;
    transform: none;
  }
}

```

5. **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`](https://github.com/emilkowalski/skills/blob/main/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`](https://github.com/emilkowalski/skills/blob/main/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`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md) explicitly lists this as a required review step.