Animation Fill Mode
Control what happens before and after an animation plays using animation-fill-mode — forwards, backwards, and both.
Animation Fill Mode
By default, when a keyframe animation ends, the element snaps back to its original state. The animation-fill-mode property controls what happens before the animation starts and after it ends.
The Problem
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.element {
opacity: 0.5; /* Original state */
animation: fadeIn 1s ease;
}
/* After animation: element snaps back to opacity: 0.5 ❌ */
The element starts at opacity: 0.5, animates from 0 to 1, then jumps back to 0.5. Not what we want!
The Solution: animation-fill-mode
| Value | Before Animation | After Animation |
|---|---|---|
none (default) | Original styles | Original styles (snaps back) |
forwards | Original styles | Keeps the final frame (to/100%) |
backwards | Applies the first frame (from/0%) | Original styles |
both | Applies the first frame | Keeps the final frame |
forwards — Keep the End State
.element {
opacity: 0;
animation: fadeIn 1s ease forwards;
}
/* After animation: stays at opacity: 1 ✅ */
This is the most commonly used fill mode. The element stays in its animation end state.
backwards — Apply the Start State During Delay
@keyframes slideIn {
from { transform: translateX(-100%); }
to { transform: translateX(0); }
}
.element {
animation: slideIn 0.5s ease 1s backwards;
/* 1s delay — during the delay, the element sits at translateX(-100%) */
}
Without backwards, the element would be visible in its original position during the delay, then suddenly jump to translateX(-100%) and animate. backwards fixes this by applying the first keyframe immediately.
both — Best of Both Worlds
.element {
animation: slideIn 0.5s ease 1s both;
/* During delay: applies from state (translateX(-100%))
After animation: keeps to state (translateX(0)) */
}
both combines forwards and backwards. Use it when you have a delay and want to keep the final state.
Visual Timeline
DELAY ──────→ ANIMATION ──────→ AFTER
none: [original] [0%→100%] [original]
forwards: [original] [0%→100%] [100%]
backwards: [0%] [0%→100%] [original]
both: [0%] [0%→100%] [100%]
Common Pattern: Staggered Entrance
@keyframes fadeSlideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
opacity: 0; /* Initial hidden state */
animation: fadeSlideUp 0.5s ease both;
}
.card:nth-child(1) { animation-delay: 0.1s; }
.card:nth-child(2) { animation-delay: 0.2s; }
.card:nth-child(3) { animation-delay: 0.3s; }
.card:nth-child(4) { animation-delay: 0.4s; }
Each card fades in one after another. both ensures they stay hidden during their delay and visible after.
Key Takeaways
- Without
fill-mode, elements snap back to their original state after animation forwards— keep the final animation state (most common)backwards— apply the first keyframe during the delay periodboth— combines forwards and backwards (safest choice)- Use
bothwith staggered delays for smooth entrance sequences