CSSanimation-play-state

animation-play-state

animation-play-state pauses or resumes an animation. Set to running (default) to play, paused to freeze at the current frame. Used to pause animations on hover, stop loaders when complete, or control animations with user interaction. Combined with transitions, it creates interactive, user-controllable animations.

Animation play state values

Value

Behavior

Use Case

running (default)

Animation plays

Normal animation

paused

Animation freezes at current frame

Pause on hover, user control

CSS
/* Running: animation plays (default) */
.running {
  animation: spin 2s linear infinite;
  animation-play-state: running;
}

/* Paused: animation stops at current frame */
.paused {
  animation: spin 2s linear infinite;
  animation-play-state: paused;
}

/* Pause on hover */
.pause-hover {
  animation: spin 2s linear infinite;
  animation-play-state: running;
}

.pause-hover:hover {
  animation-play-state: paused;
  /* Freezes animation when hovering */
}

/* Resume on mouse leave */
.pause-hover:focus {
  animation-play-state: paused;
}

/* Pause on focus (keyboard accessible) */
.accessible-pause {
  animation: pulse 1s ease-in-out infinite;
}

.accessible-pause:focus {
  animation-play-state: paused;
  /* Keyboard users can pause too */
}

/* Conditional pause based on media query */
@media (prefers-reduced-motion: reduce) {
  .respects-motion {
    animation-play-state: paused;
    /* Respects user accessibility settings */
  }
}
Practical pause/resume patterns

CSS
/* Loading indicator that pauses when complete */
.loader {
  animation: spin 1s linear infinite;
  animation-play-state: running;
}

.loader.complete {
  animation-play-state: paused;
  /* Stops spinning when operation complete */
}

/* Video play button with pause animation */
.video-thumb {
  position: relative;
}

.play-button {
  animation: pulse-button 2s ease-in-out infinite;
  animation-play-state: running;
}

.video-thumb:hover .play-button {
  animation-play-state: paused;
  /* Stops pulsing when hovering */
}

@keyframes pulse-button {
  0%, 100% { transform: scale(1); opacity: 1; }
  50% { transform: scale(1.1); opacity: 0.8; }
}

/* Animated scroll indicator */
.scroll-indicator {
  animation: bounce 1.5s ease-in-out infinite;
  animation-play-state: running;
}

@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

/* Stop animating when user starts scrolling */
window.addEventListener('scroll', () => {
  document.querySelector('.scroll-indicator').style.animationPlayState = 'paused';
});

/* Attention-grabbing animation */
.attention {
  animation: shake 0.5s ease-in-out;
  animation-play-state: running;
  animation-fill-mode: forwards;
}

.attention.acknowledged {
  animation-play-state: paused;
  /* User acknowledged, stop shaking */
}

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-5px); }
  75% { transform: translateX(5px); }
}

/* Progress bar animation */
.progress {
  animation: progress-fill 3s ease-in-out;
  animation-fill-mode: forwards;
}

.progress.paused {
  animation-play-state: paused;
  /* User paused the upload */
}

@keyframes progress-fill {
  from { width: 0%; }
  to { width: 100%; }
}
Multiple animations with play state

CSS
/* Pausing/resuming multiple animations */

.multi-animate {
  animation:
    rotate 2s linear infinite,
    bob 3s ease-in-out infinite;
  animation-play-state: running;
}

.multi-animate.paused {
  animation-play-state: paused;
  /* Both animations pause */
}

@keyframes rotate {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

@keyframes bob {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-10px); }
}

/* Different play states for different animations */
.selective {
  animation:
    fade 2s ease infinite,
    slide 3s ease infinite;
}

/* Problem: can't pause individual animations with play-state */
/* play-state applies to all animations on an element */
.selective.paused {
  animation-play-state: paused;
  /* Both animations pause */
}

/* Solution: use separate elements for independent control */
.parent {
  animation: parent-animation 2s ease infinite running;
}

.child {
  animation: child-animation 3s ease infinite running;
}

.parent.paused {
  animation-play-state: paused;
  /* Only parent pauses */
}

.child.paused {
  animation-play-state: paused;
  /* Only child pauses */
}
JavaScript integration

CSS
/* CSS for controlled animations */

.controlled-animation {
  animation: rotate 2s linear infinite;
  /* Default to running, JavaScript controls it */
}

/* JavaScript could do: */

/* Example 1: Pause on hover */
const element = document.querySelector('.loader');
element.addEventListener('mouseenter', () => {
  element.style.animationPlayState = 'paused';
});
element.addEventListener('mouseleave', () => {
  element.style.animationPlayState = 'running';
});

/* Example 2: Play/pause button */
const button = document.querySelector('#pauseButton');
const animation = document.querySelector('.spinning-element');

button.addEventListener('click', () => {
  const state = window.getComputedStyle(animation).animationPlayState;
  animation.style.animationPlayState = state === 'paused' ? 'running' : 'paused';
  button.textContent = state === 'paused' ? 'Pause' : 'Resume';
});

/* Example 3: Pause when element is not visible */
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.style.animationPlayState = 'running';
    } else {
      entry.target.style.animationPlayState = 'paused';
      /* Save resources when off-screen */
    }
  });
});

document.querySelectorAll('.animated').forEach(el => {
  observer.observe(el);
});

/* CSS with class for JS control */
.animation.active {
  animation-play-state: running;
}

.animation {
  animation-play-state: paused;
}
Accessibility and performance

CSS
/* Respect user preference for reduced motion */
@media (prefers-reduced-motion: reduce) {
  .animation {
    animation-play-state: paused !important;
    /* Pause all animations for accessibility */
  }
}

/* Allow manual pause for animations that auto-play */
.auto-playing {
  animation: scroll-hint 1.5s ease-in-out infinite;
}

.auto-playing:hover,
.auto-playing:focus {
  animation-play-state: paused;
  /* Users can pause if they find it distracting */
}

/* Pause animations that are off-screen for performance */
.off-screen {
  animation-play-state: paused;
  /* Save CPU/GPU resources */
}

/* Only run animation when needed */
.conditional {
  animation-play-state: paused;
}

.conditional.needs-attention {
  animation-play-state: running;
  /* Play only when has important message */
}

/* Avoid constant animations on static pages */
.avoid-constant {
  animation: pulse 2s ease-in-out infinite;
  animation-play-state: running;
  /* Unnecessary CPU usage */
}

/* Better: only animate on interaction */
.smart-animation {
  animation: pulse 2s ease-in-out infinite;
  animation-play-state: paused;
}

.smart-animation:hover,
.smart-animation.active {
  animation-play-state: running;
  /* Animates only on hover/interaction */
}
Advanced patterns

CSS
/* Animation pause indicator (show that animation is paused) */
.paused-indicator {
  position: relative;
}

.paused-indicator::after {
  content: '⏸ Paused';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}

.paused-indicator.paused::after {
  opacity: 1;
  /* Shows pause indicator when animation is paused */
}

/* Animated countdown that can pause */
.countdown {
  animation: count-down 60s steps(60, end) running;
  animation-fill-mode: forwards;
}

@keyframes count-down {
  from { content: '60'; }
  to { content: '0'; }
}

/* Toggle animation with visual feedback */
.toggle-animation {
  animation: toggle-spin 0.4s ease;
  animation-fill-mode: forwards;
  animation-play-state: running;
}

.toggle-animation.paused {
  animation-play-state: paused;
  /* Animation freezes at current position */
}

@keyframes toggle-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(180deg); }
}

/* Staggered animations that can be paused together */
.staggered-list {
  animation: list-entrance 0.6s ease;
  animation-fill-mode: forwards;
  animation-play-state: running;
}

.staggered-list:nth-child(1) { animation-delay: 0s; }
.staggered-list:nth-child(2) { animation-delay: 0.1s; }
.staggered-list:nth-child(3) { animation-delay: 0.2s; }

.container.paused .staggered-list {
  animation-play-state: paused;
  /* Pause entire group with one class */
}

@keyframes list-entrance {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}
Note
`animation-play-state: running` (default) plays animation, `paused` freezes at current frame. Use to: pause on hover, stop loaders when complete, pause off-screen animations for performance, respect prefers-reduced-motion. JavaScript can dynamically toggle it with `.style.animationPlayState = 'paused'`. Multiple animations on one element all pause together. Good for interactive animations and resource optimization.
Next
Animation shorthand: [animation shorthand](/css/animation-shorthand).