CSS animations require several properties to work together correctly. When an animation doesn't play, it's usually due to a missing animation-duration, a typo in the animation-name that doesn't match the @keyframes name, or the animation being paused.
/* ❌ Problem "” missing duration */
.element { animation: fadeIn; } /* Duration defaults to 0s! */
/* ✅ Solution "” always include duration */
.element {
animation: fadeIn 1s ease-in-out;
/* name | duration | timing | (optional: delay, iteration, direction) */
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* ❌ Name mismatch */
.element { animation: fade-in 1s; } /* "fade-in" */
@keyframes fadeIn { /* "fadeIn" "” different! */
from { opacity: 0; }
to { opacity: 1; }
}
/* ✅ Names must match exactly (case-sensitive) */
.element { animation: fadeIn 1s ease; }
@keyframes fadeIn { /* Exact match */
from { opacity: 0; }
to { opacity: 1; }
}
/* ✅ Loop animation infinitely */
.spinner {
animation: spin 1s linear infinite; /* infinite = loop forever */
}
/* ✅ Keep final state after animation */
.element {
animation: slideIn 0.5s ease forwards; /* forwards = keep end state */
}
/* ✅ Alternate direction */
.pulse {
animation: pulse 1s ease-in-out infinite alternate;
/* alternate = plays forward then backward */
}
/* ✅ Respect user's motion preference */
.animated {
animation: bounce 0.5s infinite;
}
@media (prefers-reduced-motion: reduce) {
.animated {
animation: none; /* Disable for users who prefer less motion */
}
}
/* ✅ Or use a subtle alternative */
@media (prefers-reduced-motion: reduce) {
.animated {
animation-duration: 0.01ms; /* Effectively instant */
}
}
/* ✅ Complete animation with all properties */
.notification {
animation-name: slideDown;
animation-duration: 0.4s;
animation-timing-function: ease-out;
animation-delay: 0.1s;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: forwards;
animation-play-state: running;
/* Shorthand: name duration timing delay count direction fill-mode */
animation: slideDown 0.4s ease-out 0.1s 1 normal forwards;
}
@keyframes slideDown {
from { transform: translateY(-100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
Most common causes: missing animation-duration (defaults to 0s), typo in animation-name that doesn't match @keyframes, or animation-play-state: paused.
Add animation-iteration-count: infinite, or use the shorthand: animation: myAnim 1s linear infinite.
Use animation-fill-mode: forwards. This keeps the element in the state defined by the last keyframe after the animation completes.
It's a media query that detects if the user has requested reduced motion in their OS settings. Always provide a no-animation fallback for accessibility.
transform and opacity are the most performant "” they're GPU-accelerated and don't trigger layout reflow. Avoid animating width, height, margin, or padding as they cause expensive reflows.
Explore 500+ free tutorials across 20+ languages and frameworks.