Fix: CSS Animation Not Working (@keyframes Has No Effect)
Quick Answer
How to fix CSS animations not working — @keyframes not applying, animation paused, transform conflicts, reduced motion settings, and common animation property mistakes.
The Error
You define a CSS animation with @keyframes but the element does not animate. It stays static, flickers once, or animates only in one direction without repeating. No error appears — the animation just has no visible effect.
Common symptoms:
animationproperty is set but the element does not move.- The animation plays once and stops when you expected
infinite. animation-durationis set but the animation is instantaneous.- The element jumps to the final state without transitioning.
- Animation works in Chrome but not Safari, or vice versa.
- Animation stops working after adding
transformorpositionto the element.
Why This Happens
CSS animations require both a @keyframes rule and an animation property on the element. They fail when:
animation-durationis missing or0s— the default duration is0s, so the animation completes instantly.@keyframesname does not match theanimation-namevalue — a typo makes them invisible to each other.animation-fill-modeis not set — after the animation ends, the element snaps back to its original state.display: noneorvisibility: hidden— animations do not run on hidden elements.prefers-reduced-motionmedia query is active — users with motion sensitivity may have system animations disabled.will-changeortransformon an ancestor creates a stacking context that interferes.- The animated property is not animatable — some CSS properties cannot be animated.
Fix 1: Ensure animation-duration Is Set
The most common mistake: animation-duration defaults to 0s, so the animation completes in zero time — invisible to the eye.
Broken — no duration:
.box {
animation-name: slide-in;
/* Missing animation-duration — defaults to 0s */
}
@keyframes slide-in {
from { transform: translateX(-100px); }
to { transform: translateX(0); }
}Fixed — always specify duration:
.box {
animation: slide-in 0.5s ease-out;
/* shorthand: name | duration | easing */
}
@keyframes slide-in {
from { transform: translateX(-100px); }
to { transform: translateX(0); }
}Animation shorthand property order:
animation: name duration timing-function delay iteration-count direction fill-mode play-state;
/* Examples */
animation: fade-in 1s ease 0s 1 normal forwards running;
animation: spin 2s linear infinite;
animation: bounce 0.5s ease-in-out 3;Pro Tip: Always use the
animationshorthand instead of individual properties — it is harder to accidentally omitanimation-durationwhen you must write it explicitly in the shorthand. The only exception is when you need to override a single animation property later.
Fix 2: Verify @keyframes Name Matches animation-name
The name in @keyframes must exactly match the animation-name value (case-sensitive):
Broken — name mismatch:
/* Defined as slide-in */
@keyframes slide-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* Referenced as slideIn — does not match */
.box {
animation: slideIn 1s ease;
}Fixed — exact match:
@keyframes slideIn {
from { opacity: 0; }
to { opacity: 1; }
}
.box {
animation: slideIn 1s ease;
}Verify with DevTools: Open Chrome DevTools → Elements → select the animated element → Animations panel (in the three-dot menu → More tools → Animations). If the animation name shows as “invalid”, the keyframes rule is not found.
Fix 3: Fix animation-fill-mode for End State
By default, elements return to their original state after an animation ends. To keep the element at its final animated state, set animation-fill-mode: forwards:
Broken — element snaps back after animation:
.box {
animation: fade-in 1s ease;
/* After 1s, opacity returns to its original value */
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}Fixed — keep the final state:
.box {
opacity: 0; /* Start hidden */
animation: fade-in 1s ease forwards;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}animation-fill-mode values:
none(default): element reverts to original state before and after animation.forwards: element keeps the final keyframe state after animation ends.backwards: element applies the first keyframe state during the delay period.both: combines forwards and backwards.
Fix 4: Fix Infinite Animations That Stop
If animation-iteration-count: infinite is set but the animation stops after one cycle:
/* Broken — conflicting properties */
.spinner {
animation: spin 1s linear infinite;
animation-fill-mode: forwards; /* Conflicts with infinite — stops after first iteration */
}animation-fill-mode: forwards combined with animation-iteration-count: infinite causes the animation to stop after the first iteration in some browsers. Remove fill-mode for infinite animations:
.spinner {
animation: spin 1s linear infinite;
/* No fill-mode needed for infinite animations */
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}Fix 5: Respect prefers-reduced-motion
Users can request reduced motion at the OS level (Settings → Accessibility → Reduce Motion). CSS respects this with the prefers-reduced-motion media query. If your animation does not run, check if the user has reduced motion enabled:
/* Default: animate */
.box {
animation: slide-in 0.5s ease forwards;
}
/* Disable animation for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
.box {
animation: none;
/* Or provide a simplified alternative: */
opacity: 1;
transform: none;
}
}Check if this is the cause: Open Chrome DevTools → Rendering panel (three-dot → More tools → Rendering) → “Emulate CSS media feature prefers-reduced-motion” → Toggle to reduce. If the animation stops, this is the cause.
Best practice — design animations with reduced motion in mind:
@keyframes slide-in {
from { transform: translateX(-100px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.box {
animation: slide-in 0.5s ease forwards;
}
@media (prefers-reduced-motion: reduce) {
.box {
animation: fade-in 0.2s ease forwards; /* Simpler fade instead of slide */
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
}Fix 6: Fix transform Conflicts and Stacking Context
When using transform in @keyframes on an element that also has a transform on its parent or itself, animations can behave unexpectedly:
Broken — transform on parent affects child animation:
.parent {
transform: translateY(50px); /* Creates stacking context */
}
.child {
animation: move-right 1s ease infinite;
}
@keyframes move-right {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}
/* Works, but the child's transform is relative to its own coordinate space,
which is already offset by the parent's transform */For elements that need both a static transform and an animation:
/* Use a wrapper element for the static positioning */
.wrapper {
transform: translateY(50px); /* Static position */
}
.animated {
animation: move-right 1s ease infinite; /* Animation */
}
@keyframes move-right {
from { transform: translateX(0); }
to { transform: translateX(100px); }
}Alternatively, combine transforms in keyframes:
@keyframes move-right-and-offset {
from { transform: translateY(50px) translateX(0); }
to { transform: translateY(50px) translateX(100px); }
}Fix 7: Fix Safari-Specific Animation Issues
Safari sometimes requires -webkit- prefixes for older animation properties, and has quirks with certain keyframe configurations:
/* Add webkit prefix for maximum compatibility */
@-webkit-keyframes slide-in {
from { -webkit-transform: translateX(-100px); transform: translateX(-100px); }
to { -webkit-transform: translateX(0); transform: translateX(0); }
}
@keyframes slide-in {
from { transform: translateX(-100px); }
to { transform: translateX(0); }
}
.box {
-webkit-animation: slide-in 0.5s ease forwards;
animation: slide-in 0.5s ease forwards;
}Modern Safari (15+) does not require -webkit- for most animation properties. Use Autoprefixer in your build pipeline to add prefixes automatically.
Safari animation with position: sticky or backface-visibility:
/* Safari sometimes needs this to enable hardware acceleration for animations */
.animated {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
animation: slide-in 0.5s ease;
}Debugging CSS Animations
Use Chrome DevTools Animations panel:
- Open DevTools → three-dot menu → More tools → Animations.
- Trigger the animation on the page.
- The panel shows a timeline of all active animations, their duration, keyframes, and playback state.
- Click on an animation to slow it down (0.1×, 0.25×) for inspection.
Pause and replay animations:
// In the browser console — pause all animations on an element
document.querySelector(".box").getAnimations().forEach(a => a.pause());
// Get animation details
document.querySelector(".box").getAnimations().forEach(a => {
console.log(a.animationName, a.playState, a.currentTime);
});Force an animation to replay by toggling the element:
const el = document.querySelector(".box");
el.classList.remove("animate");
void el.offsetWidth; // Trigger reflow — forces browser to re-evaluate
el.classList.add("animate");Still Not Working?
Check animation-play-state. If animation-play-state: paused is set (by JavaScript or another CSS rule), the animation is frozen. Check computed styles in DevTools.
Check display: none ancestors. Animations on or inside display: none elements do not run. If you are animating an element into view, start from opacity: 0 or transform: scale(0) rather than display: none.
Check if the property is animatable. Not all CSS properties can be animated. display, font-family, and border-radius with border-* shorthand have quirks. Stick to transform, opacity, color, background-color, width, height, top, left for reliable animations.
Use opacity and transform for performance. Animating width, height, top, left, or margin triggers layout recalculation (reflow) on every frame — this is expensive. Animate transform and opacity instead — they run on the GPU and do not trigger reflow.
For related CSS layout issues, see Fix: CSS Flexbox not working and Fix: CSS position sticky not working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: CSS Custom Properties (Variables) Not Working or Not Updating
How to fix CSS custom properties not applying — wrong scope, missing fallback values, JavaScript not setting variables on the right element, and how CSS variables interact with media queries and Shadow DOM.
Fix: CSS position: sticky Not Working
How to fix CSS position sticky not working — element scrolls away instead of sticking, caused by overflow hidden on a parent, missing top value, wrong stacking context, or incorrect height on the container.
Fix: CSS Grid Not Working (display: grid Has No Effect)
How to fix CSS Grid not working — grid-template-columns has no effect, grid items not placing correctly, implicit vs explicit grid confusion, and common grid layout debugging techniques.
Fix: CSS Flexbox Not Working (display: flex Has No Effect)
How to fix CSS Flexbox not working — why display flex has no effect, flex children not aligning, flex-grow not stretching, and how to debug common Flexbox layout issues.