Difficulty: Intermediate
Beyond the basic animation-name, animation-duration, and animation-iteration-count covered in the keyframes lesson, CSS provides several additional properties that give you fine-grained control over how animations behave before, during, and after playback. Understanding these properties is the difference between a functional animation and a polished, production-ready one.
The animation-fill-mode property is arguably the most important of the advanced animation properties. It controls what CSS values are applied to the element before and after the animation runs. The default value none means the element reverts to its pre-animation styles after the animation completes. The value forwards retains the final keyframe values after the animation ends - essential when your animation moves an element to its final position. The value backwards applies the first keyframe values during the animation-delay period before the animation starts. The value both combines forwards and backwards behavior.
The animation-direction property controls whether the animation plays forward, backward, or alternates. The default normal plays from 0% to 100%. The value reverse plays from 100% to 0%. The value alternate plays forward on odd iterations and backward on even iterations, creating a smooth back-and-forth effect without needing to define return keyframes. The value alternate-reverse starts backward and alternates. Alternate is particularly useful for breathing effects, pulsing, and pendulum-like motions.
The animation-play-state property toggles between running and paused. This is useful for pause/play controls, reducing motion when the user scrolls past an animated section, or responding to user preferences. Combined with :hover, you can create animations that pause when the user hovers (or vice versa). It is also useful for the prefers-reduced-motion media query, where you can set animation-play-state: paused to respect user accessibility preferences.
The animation shorthand combines all animation properties in a single declaration. The recommended order is: name, duration, timing-function, delay, iteration-count, direction, fill-mode, play-state. Multiple animations can be comma-separated. While the shorthand is convenient, the longhand properties are sometimes clearer for complex animations with many settings. A common production pattern is to define reusable animation utilities (like .animate-fade-in, .animate-slide-up) with sensible defaults that can be customized with CSS custom properties for duration, delay, and timing.
@keyframes slideRight {
from { transform: translateX(0); }
to { transform: translateX(200px); }
}
/* none (default): Reverts to original position after animation */
.fill-none {
animation: slideRight 1s ease;
animation-fill-mode: none;
/* After: snaps back to translateX(0) */
}
/* forwards: Keeps the final keyframe values */
.fill-forwards {
animation: slideRight 1s ease;
animation-fill-mode: forwards;
/* After: stays at translateX(200px) */
}
/* backwards: Applies first keyframe during delay */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.fill-backwards {
opacity: 1; /* Initial value */
animation: fadeIn 1s ease 2s; /* 2s delay */
animation-fill-mode: backwards;
/* During 2s delay: opacity is 0 (from first keyframe) */
/* Without backwards: opacity would be 1 during delay */
}
/* both: Combines forwards + backwards */
.fill-both {
animation: slideRight 1s ease 0.5s;
animation-fill-mode: both;
/* During delay: at translateX(0) */
/* After: stays at translateX(200px) */
}
fill-mode controls the element's state before the delay period (backwards) and after the animation ends (forwards). 'forwards' is the most commonly used - without it, elements snap back to their pre-animation state. 'both' is the safest choice when using animation-delay.
@keyframes slideX {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}
/* normal: 0% -> 100%, 0% -> 100%, ... */
.direction-normal {
animation: slideX 1s ease infinite;
animation-direction: normal;
}
/* reverse: 100% -> 0%, 100% -> 0%, ... */
.direction-reverse {
animation: slideX 1s ease infinite;
animation-direction: reverse;
}
/* alternate: 0%->100%, 100%->0%, 0%->100%, ... */
.direction-alternate {
animation: slideX 1s ease infinite;
animation-direction: alternate;
}
/* Practical: Breathing/pulsing effect */
@keyframes breathe {
from {
transform: scale(1);
opacity: 0.7;
}
to {
transform: scale(1.05);
opacity: 1;
}
}
.breathing-element {
animation: breathe 2s ease-in-out infinite alternate;
}
/* Pendulum swing */
@keyframes pendulum {
from { transform: rotate(-30deg); }
to { transform: rotate(30deg); }
}
.pendulum {
transform-origin: top center;
animation: pendulum 1.5s ease-in-out infinite alternate;
}
alternate creates smooth back-and-forth motion by automatically reversing on even iterations. This eliminates the need to define return keyframes manually. Combined with infinite and ease-in-out, it creates natural oscillating motions like breathing and swinging.
/* Pause animation on hover */
.carousel {
animation: scroll 20s linear infinite;
}
.carousel:hover {
animation-play-state: paused;
}
/* Respect reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Alternative: Pause specific animations */
@media (prefers-reduced-motion: reduce) {
.animated-element {
animation-play-state: paused;
}
/* Keep essential animations but simplify them */
.loading-spinner {
animation-duration: 1.5s; /* Slower, less jarring */
}
}
/* Toggle play/pause with JavaScript */
<button onclick="toggleAnimation()">Pause/Play</button>
<style>
.paused {
animation-play-state: paused;
}
</style>
<script>
function toggleAnimation() {
document.querySelector('.animated')
.classList.toggle('paused');
}
</script>
animation-play-state: paused freezes the animation at its current position. The prefers-reduced-motion media query detects users who have enabled reduced motion in their OS settings - always respect this preference by slowing or stopping decorative animations.
/* Shorthand order:
name | duration | timing | delay | count | direction | fill | play-state */
/* Single animation shorthand */
.element {
animation: fadeIn 0.5s ease-out 0.2s 1 normal forwards running;
}
/* Common shorthand patterns */
.fade-in {
animation: fadeIn 0.5s ease-out forwards;
}
.spin {
animation: rotate 1s linear infinite;
}
.pulse {
animation: pulse 2s ease-in-out infinite alternate;
}
/* Multiple animations on one element */
.fancy-entrance {
animation:
fadeIn 0.5s ease-out forwards,
slideUp 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards,
glow 2s ease-in-out 0.5s infinite alternate;
}
/* Reusable animation utilities with custom properties */
.animate {
--duration: 0.5s;
--delay: 0s;
--timing: ease-out;
}
.animate-fade-in {
animation: fadeIn var(--duration) var(--timing) var(--delay) forwards;
}
.animate-slide-up {
animation: slideUp var(--duration) var(--timing) var(--delay) forwards;
}
/* Override timing per element */
.hero-title {
--duration: 0.8s;
--delay: 0.3s;
}
The shorthand packs all animation properties into one line. Multiple animations are comma-separated. Using CSS custom properties creates reusable animation utilities where individual elements can customize timing and delay without rewriting the animation declaration.
animation-fill-mode, animation-direction, animation-play-state, animation-delay, Shorthand