A notification that slides in from the top of the screen, then vanishes instantly once the animation ends, snapping back to its invisible starting position: this surprising behavior, very common among beginners, almost always comes from forgetting the animation-fill-mode property.
By default, a CSS animation has no memory of what happens before it starts or after it ends: it plays its sequence, then the element goes right back to looking exactly as it would have without it. animation-fill-mode changes that by extending the effect of the animation's first or last frame beyond its actual duration.
Definition
animation-fill-mode decides which CSS values an element keeps before an animation starts and after it finishes. It accepts four main values: none, which changes nothing (the default behavior), forwards, backwards, and both, which combines the two.
| Value | Effect |
|---|---|
none | No effect kept, the default behavior |
forwards | Keeps the last frame's state after it ends |
backwards | Applies the first frame's state before it starts |
both | Combines forwards and backwards |
forwards: keeping the last state
forwards is the value used most in practice: it makes the element keep the styles defined in the last step of the CSS animation once it finishes, instead of instantly reverting to whatever it looked like before the animation.
@keyframes slide-in {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.notification {
animation: slide-in 0.4s ease-out forwards;
}backwards: applying the first state before the start
backwards is mostly used together with animation-delay: it applies the first step's styles right from page load, even before the delay has run out and the animation actually begins. Without it, the element briefly shows its natural final state before suddenly switching to the animation's starting state the moment it kicks off.
The first-frame jump pitfall
The most common issue shows up exactly with delayed animations using animation-delay. Without backwards, the element stays visible in its normal state throughout the delay, then jumps abruptly to the animation's starting state the instant it begins, an unpleasant and entirely avoidable visual glitch.
animation-delay, set animation-fill-mode to both instead of forwards alone. both applies the starting state during the delay and keeps the final state after it ends, avoiding a visual jump at both the beginning and the end of the animation.A practical shorthand inside the animation property
animation-fill-mode rarely gets written on its own: it usually gets folded directly into the animation shorthand, after the duration, the Easing and timing function and any delay, as in animation: slide-in 0.4s ease-out 0.2s both.
This setting is especially useful for a stack of notifications staggered with different delays, where each one needs to stay invisible before its turn instead of flashing on page load.
Frequently asked questions
animation-fill-mode: forwards and just setting the final styles directly in CSS?The final visual result is identical, but forwards avoids duplicating values in two different places: without it, the last frame's styles would need to be manually repeated outside @keyframes, risking the two versions drifting apart after a future change.
animation-fill-mode work with a transition instead of an animation?No, this property only exists for the animation and @keyframes family. A transition does not have this first-frame jump problem since it always starts from the CSS state actually applied to the element at the moment of the change, never recreating an artificial state.
both always the safest default value?In the vast majority of cases, yes, unless the element is deliberately meant to show its natural state during the delay before the animation, which remains a legitimate design choice but a rarer need than hiding that delay with backwards.