skip to content

In CSS, what does animation-fill-mode do, and what are the practical differences between none, forwards, backwards, and both?

level: middleimportance: must knowfreq 62%

answer

  1. controls the interval outside the run
  2. default is none: nothing before or after
  3. the delay window versus after the end
  4. forwards means last keyframe encountered
  5. direction decides which keyframe that is

basics

~20 s

animation-fill-mode decides whether keyframe values apply outside the animation's active run. The default none applies nothing before or after; forwards holds the final keyframe after it ends; backwards applies the first keyframe during the delay; both does each.

solid answer

~50 s

An animation only controls its element between its start and its end. `animation-fill-mode` extends that control outward. With the default `none`, the element renders with its cascaded values during `animation-delay` and again the instant the animation finishes — which is why a fade-out with no fill mode snaps back to full opacity at the end. `forwards` keeps the values from the last keyframe of the final iteration applied indefinitely. `backwards` applies the first keyframe's values immediately, during the delay, so a delayed fade-in does not sit fully visible while it waits. `both` does the two together, which is the usual choice for delayed entrance animations. The key subtlety is that a filled value does not change the element's cascaded value — it is still an animated value sitting above normal declarations, so a plain rule cannot override it, but an `!important` one can.

code

css · 14 lines
css
@keyframes fade-in-up {
  from { opacity: 0; translate: 0 12px; }
  to   { opacity: 1; translate: 0 0; }
}

.toast {
  opacity: 1;
  animation: fade-in-up 300ms ease-out 400ms both;
}

.pinned-open {
  /* even iteration count + alternate ends on the 0% keyframe */
  animation: fade-in-up 300ms ease-out 2 alternate forwards;
}

go deeper

for a junior

Know that by default an animation leaves no trace once it ends, and that animation-fill-mode: forwards is what makes the final state stick. Being able to name the four values is enough here.

for a middle

Explain the active interval and what each value does on either side of it, and work out which keyframe forwards holds once animation-direction: alternate and the iteration count are involved.

for a senior

Demonstrate the cascade consequence — a filled value outranks normal declarations, so later classes appear ignored — and argue for ending the animation in the resting state or applying an end-state class on animationend instead.

for a principal

Own the convention across a codebase: whether persisted end states are expressed as fills or as cascade state, since fills scattered through a stylesheet make element state unreadable from the rules alone.

## The active interval A keyframe animation has an *active interval*: it starts after `animation-delay` and ends after `animation-duration × animation-iteration-count`. Inside that interval, the animation drives every property that appears in its `@keyframes` rule. Outside it — before the start and after the end — the animation contributes nothing at all by default, and the element renders with the values the cascade gives it. `animation-fill-mode` is the switch for what happens outside that interval. ## The four values **`none`** (the initial value): the animation contributes nothing before or after. This produces the two classic surprises. A fade-out animation reaches `opacity: 0` at the last keyframe and then, one frame later, snaps back to the element's declared opacity — usually `1`. And a delayed fade-in sits fully visible during its delay, then jumps to `opacity: 0` when the animation actually starts. **`forwards`**: after the active interval ends, the element keeps the values from the last keyframe *encountered* on the final iteration. That wording matters — see the direction interaction below. **`backwards`**: during the delay period, the element already renders with the values of the first keyframe it *will* use. That is normally the `0%` block, but with `animation-direction: reverse` or `alternate-reverse` the first keyframe it will use is `100%`. **`both`**: `backwards` behaviour before, `forwards` behaviour after. ```css @keyframes fade-in-up { from { opacity: 0; translate: 0 12px; } to { opacity: 1; translate: 0 0; } } .toast { animation: fade-in-up 300ms ease-out 400ms both; } ``` Without `both`, this toast would be fully opaque and in place for 400ms, then jump down and vanish before animating in. ## The direction and iteration-count interaction `forwards` does *not* mean "the 100% keyframe". It means the end of the last iteration, which depends on `animation-direction` and `animation-iteration-count`: - `normal`, any whole count → ends at `100%`. - `reverse` → every iteration runs backwards, so it ends at `0%`. - `alternate` with an **even** count → the final iteration ran backwards, so it ends at `0%`. - `alternate` with an **odd** count → ends at `100%`. A fractional `animation-iteration-count` fills with the value at that fractional point: `animation: slide 2s 0.5 forwards` holds the interpolated midpoint of the keyframes forever. ## Filled values and the cascade A filled value is still an *animated* value. In the cascade, animations sit above normal author declarations, so while a `forwards` fill is in effect, a plain rule targeting the same property does not visibly win no matter how specific it is: ```css .panel { opacity: 1; } /* loses to the fill */ .panel.hidden { opacity: 1 !important; } /* wins — important author beats animations */ ``` The element's *cascaded* value never changed. `element.style` is still empty, so script that reads the inline style sees nothing, while `getComputedStyle` reports the filled value. This trips people up when they try to "take over" from an animation by setting a class. ## When forwards is the wrong tool `forwards` keeps the animation permanently in effect on that element, and it keeps overriding those properties for the element's whole life. For a one-shot entrance that ends in the element's natural resting state, the cleaner pattern is to make the keyframes end *at* the declared value, so removing the fill changes nothing. For a state change that must persist and later be overridden by other rules — an element that ends up hidden, disabled, or collapsed — the more maintainable pattern is to listen for the `animationend` event and apply a class that sets the final state as a normal declaration. Then the state lives in the cascade, where the rest of your stylesheet can reason about it. ## Quick diagnosis "My animation flashes back at the end" → you need `forwards`, or keyframes that end at the resting value. "My delayed animation shows the final state while it waits" → you need `backwards`. "My class can't override the finished animation" → a `forwards` fill is still applied; drop the fill and apply an end-state class instead.

  • With animation: slide 1s 2 alternate forwards, which keyframe's values persist after it finishes?
    The `0%` keyframe. `alternate` runs the first iteration forwards and the second backwards, so the final iteration ends where the animation started. `forwards` holds the last keyframe *encountered*, not the `100%` block. With an odd iteration count — `3 alternate` — it would hold `100%` instead.
  • Why can a class you add after the animation ends fail to change the element?
    Because a `forwards` fill is still applied, and animated values sit above normal author declarations in the cascade. The new class's declarations lose even at higher specificity. Either give them `!important`, or drop the fill and set the end state as a normal declaration applied on `animationend`, so the value lives in the cascade instead.
  • Does animation-fill-mode: forwards change the element's inline style?
    No. It contributes an animated value in the cascade, nothing more. `element.style` stays empty and the declared rules are unchanged; only `getComputedStyle` reflects the filled value. Script that expects to read the final state off the inline style will find nothing there.

saying these in an interview costs you the question

  • Believing forwards always holds the 100% keyframe
  • Thinking fill-mode affects the animation's own timing
  • Saying backwards plays the animation in reverse
  • Assuming a forwards fill writes an inline style
  • Expecting the last keyframe to stick by default

context