animation-fill-mode
animation-fill-mode controls what styles an element has before the animation starts and after it ends. Options are none (default, reverts to original), forwards (stays at final state), backwards (applies starting state immediately), both (applies both). Essential for animations that shouldn't snap back, delayed animations that should show initial state early, and entrance/exit animations.
Fill mode values
Value | Before Start | After End | Use Case |
|---|---|---|---|
none (default) | Original styles | Original styles | Temporary animations |
forwards | Original styles | Final frame | Animations that persist (buttons, toggles) |
backwards | First frame | Original styles | Delayed animations with easing in |
both | First frame | Final frame | Entrance/exit animations, delayed |
CSS
/* none: default, no fill */
.none-fill {
opacity: 0;
/* Starts as opacity: 0 in CSS */
animation: fade-in 1s ease;
animation-fill-mode: none;
/* Before animation: opacity 0 */
/* During animation: fades to 1 */
/* After animation: back to opacity 0 */
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* forwards: stays at final state */
.forwards-fill {
animation: fade-in 1s ease;
animation-fill-mode: forwards;
/* Before animation: original styles */
/* During animation: fades from 0 to 1 */
/* After animation: stays at opacity 1 */
}
/* backwards: applies starting state immediately */
.backwards-fill {
animation: fade-in 1s ease;
animation-delay: 0.5s;
animation-fill-mode: backwards;
/* Before animation (before delay): opacity 0 (first frame) */
/* During delay: shows opacity 0 */
/* During animation: fades to 1 */
/* After animation: back to original opacity */
}
/* both: combines forwards and backwards */
.both-fill {
animation: fade-in 1s ease;
animation-delay: 0.5s;
animation-fill-mode: both;
/* Before animation (before delay): opacity 0 (first frame) */
/* During delay: shows opacity 0 */
/* During animation: fades to 1 */
/* After animation: stays at opacity 1 (final frame) */
}Practical examples
CSS
/* Button entrance: use forwards to stay transformed */
.button-entrance {
animation: button-pop 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
animation-fill-mode: forwards;
/* Bounces in and stays at normal size */
}
@keyframes button-pop {
from { transform: scale(0); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
/* Modal fade-in: use forwards to stay visible */
.modal {
animation: fade-in 0.3s ease;
animation-fill-mode: forwards;
/* Fades in and stays visible */
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* Delayed entrance with backwards: shows initial state during delay */
.delayed-entrance {
animation: slide-up 0.6s ease;
animation-delay: 0.3s;
animation-fill-mode: both;
/* Shows starting position (y: 30px) during delay */
/* Slides up during animation */
/* Stays at final position after */
}
@keyframes slide-up {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* Loading spinner: use none (animation loops) */
.spinner {
animation: spin 1s linear infinite;
animation-fill-mode: none;
/* No fill needed for infinite animations */
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Toggle animation: use forwards to persist */
.toggle {
background: white;
animation: toggle-change 0.3s ease;
animation-fill-mode: forwards;
/* Animates to new color and stays */
}
.toggle.active {
animation: toggle-active 0.3s ease;
animation-fill-mode: forwards;
/* Different animation for toggling */
}
@keyframes toggle-change {
from { background: white; }
to { background: #f0f0f0; }
}
@keyframes toggle-active {
from { background: #f0f0f0; }
to { background: #3498db; }
}Animation fill mode with delays
CSS
/* The difference between fill modes with delay */
/* SCENARIO: animation with animation-delay: 0.5s */
/* none: element invisible during delay */
.none {
animation: fade-in 1s ease;
animation-delay: 0.5s;
animation-fill-mode: none;
/* Time 0-0.5s: shows original style (invisible) */
/* Time 0.5-1.5s: animates */
/* Time 1.5s+: back to original (invisible) */
}
/* forwards: element invisible during delay, stays visible after */
.forwards {
animation: fade-in 1s ease;
animation-delay: 0.5s;
animation-fill-mode: forwards;
/* Time 0-0.5s: shows original style (invisible) */
/* Time 0.5-1.5s: animates */
/* Time 1.5s+: stays at final state (visible) */
}
/* backwards: element visible during delay, back to original after */
.backwards {
animation: fade-in 1s ease;
animation-delay: 0.5s;
animation-fill-mode: backwards;
/* Time 0-0.5s: shows first frame (visible) */
/* Time 0.5-1.5s: animates */
/* Time 1.5s+: back to original style (invisible) */
}
/* both: element visible during delay, stays visible after */
.both {
animation: fade-in 1s ease;
animation-delay: 0.5s;
animation-fill-mode: both;
/* Time 0-0.5s: shows first frame (visible) */
/* Time 0.5-1.5s: animates */
/* Time 1.5s+: stays at final state (visible) */
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* Staggered list with delayed entrance and forwards */
.list-item {
animation: list-entrance 0.5s ease;
animation-fill-mode: both;
/* Each item shows start position during delay, */
/* animates in, and stays in final position */
}
.list-item:nth-child(1) { animation-delay: 0s; }
.list-item:nth-child(2) { animation-delay: 0.1s; }
.list-item:nth-child(3) { animation-delay: 0.2s; }
.list-item:nth-child(4) { animation-delay: 0.3s; }
@keyframes list-entrance {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Page load animations: use both for best effect */
.page-element {
animation: entrance 0.6s cubic-bezier(0.4, 0, 0.2, 1);
animation-delay: calc(var(--index) * 0.1s);
animation-fill-mode: both;
/* Each element appears to load in sequence */
}Common gotchas and solutions
CSS
/* ❌ GOTCHA: Animation snaps back when it shouldn't */
.snaps-back {
animation: fade-in 1s ease;
animation-fill-mode: none;
/* After animation, element becomes invisible again */
}
/* ✓ SOLUTION: Use forwards */
.stays-visible {
animation: fade-in 1s ease;
animation-fill-mode: forwards;
/* Stays in final state */
}
/* ❌ GOTCHA: Delayed animation looks glitchy */
.glitchy {
opacity: 0;
animation: fade-in 1s ease;
animation-delay: 0.5s;
animation-fill-mode: none;
/* Element is invisible during delay (jarring) */
}
/* ✓ SOLUTION: Use both for smooth entrance */
.smooth-delayed {
animation: fade-in 1s ease;
animation-delay: 0.5s;
animation-fill-mode: both;
/* Element shows starting state during delay (smooth) */
}
/* ❌ GOTCHA: Multiple animations fighting */
.fighting {
animation: scale-up 1s ease forwards;
animation: fade-in 1s ease forwards; /* Overwrites scale-up */
}
/* ✓ SOLUTION: Use animation shorthand or separate them */
.correct {
animation:
scale-up 1s ease forwards,
fade-in 1s ease forwards;
/* Both animations play */
}
/* ❌ GOTCHA: Infinite animation with forwards/backwards (no effect) */
.no-effect {
animation: spin 2s linear infinite;
animation-fill-mode: forwards;
/* forwards/backwards/both don't matter for infinite */
}
/* ✓ FINE: Infinite animations don't need fill-mode */
.infinite {
animation: spin 2s linear infinite;
/* No fill-mode needed */
}
/* Common pattern: entrance + persistence */
@keyframes entrance {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
.element {
animation: entrance 0.5s ease;
animation-fill-mode: forwards;
/* Animates in and stays */
}Combining with animation-play-state
CSS
/* Fill mode works with paused animations */
.pausable {
animation: pulse 2s ease-in-out infinite;
animation-fill-mode: forwards;
animation-play-state: running;
}
.pausable:hover {
animation-play-state: paused;
/* Pauses at current frame, stays there */
/* fill-mode determines what happens when restarted */
}
/* Animation fill with JavaScript control */
.js-controlled {
animation: fade-in 0.5s ease;
animation-fill-mode: forwards;
animation-play-state: paused;
/* Starts paused, visible at start frame with backwards */
/* Can be played on-demand */
}
/* Progressive enhancement */
.progressive {
/* CSS fallback */
transition: opacity 0.3s ease;
/* Enhanced with animation */
animation: fade-in 0.5s ease;
animation-fill-mode: forwards;
/* Better visual effect when animations supported */
}Note
`animation-fill-mode` controls styles before/after animation: `none` = original (default), `forwards` = stays at final state (use for persistent changes), `backwards` = applies first frame immediately (use for eased-in delays), `both` = applies first frame and stays at final state (best for delayed entrances). Never needed for infinite animations. Use forwards for entrances/exits that should persist. Use both with animation-delay for smooth staggered reveals.
Next
Animation play state: [animation-play-state](/css/animation-play-state).