animation-fill-mode
Setting how a CSS animation applies styles to its target before and after its execution.
Overview
| Class | Declarations |
|---|---|
animation-fill-mode:none | animation-fill-mode: none;
|
animation-fill-mode:forwards | animation-fill-mode: forwards;
|
animation-fill-mode:backwards | animation-fill-mode: backwards;
|
animation-fill-mode:both | animation-fill-mode: both;
|
animation-fill-mode:<mode>,<…> | animation-fill-mode: <mode>,<…>;
|
Examples
Keep the final keyframe
Compare no fill with forwards fill after a one-second movement. The ordinary position is 0px; the keyframes run from 24px to 96px.
@theme { @keyframes layer-slide { from { transform: translateX(24px); } to { transform: translateX(96px); } }}<!-- None · returns to base --><div class="w:240px max-w:100%"> <p class="mb-sm font-mono font-xs text-muted">Base 0 · start 24 · end 96px</p> <div aria-hidden="true" class="h:56px p-xs outline:1px|dashed|var(--color-line-muted)"> <span id="target" class="grid place-items:center h:40px w:48px b:1px|solid|var(--color-blue-60) r-sm bg-blue-10 fg-blue-80 transform:none animation-duration:1s animation-fill-mode:none animation-name:layer-slide animation-timing-function:linear animation:none@print animation-play-state:paused@reduce-motion">→</span> </div></div><!-- Forwards · retains end --><div class="w:240px max-w:100%"> <p class="mb-sm font-mono font-xs text-muted">Base 0 · start 24 · end 96px</p> <div aria-hidden="true" class="h:56px p-xs outline:1px|dashed|var(--color-line-muted)"> <span id="target" class="grid place-items:center h:40px w:48px b:1px|solid|var(--color-blue-60) r-sm bg-blue-10 fg-blue-80 transform:none animation-duration:1s animation-fill-mode:forwards animation-name:layer-slide animation-timing-function:linear animation:none@print animation-play-state:paused@reduce-motion">→</span> </div></div>Generated CSS
@layer utilities { .animation-fill-mode\:forwards { animation-fill-mode: forwards }}Forwards retains the value where the final iteration ends. Direction and a fractional iteration count can change that value; it is not always the 100% keyframe.
Apply both before and after states
Both specimens wait 600ms. Backwards fill applies the 24px starting keyframe during that delay; both also retains the 96px ending keyframe after completion.
@theme { @keyframes layer-slide { from { transform: translateX(24px); } to { transform: translateX(96px); } }}<!-- Backwards · starting state only --><div class="w:240px max-w:100%"> <p class="mb-sm font-mono font-xs text-muted">Base 0 · start 24 · end 96px</p> <div aria-hidden="true" class="h:56px p-xs outline:1px|dashed|var(--color-line-muted)"> <span id="target" class="grid place-items:center h:40px w:48px b:1px|solid|var(--color-blue-60) r-sm bg-blue-10 fg-blue-80 transform:none animation-delay:600ms animation-duration:1s animation-fill-mode:backwards animation-name:layer-slide animation-timing-function:linear animation:none@print animation-play-state:paused@reduce-motion">→</span> </div></div><!-- Both · starting and ending states --><div class="w:240px max-w:100%"> <p class="mb-sm font-mono font-xs text-muted">Base 0 · start 24 · end 96px</p> <div aria-hidden="true" class="h:56px p-xs outline:1px|dashed|var(--color-line-muted)"> <span id="target" class="grid place-items:center h:40px w:48px b:1px|solid|var(--color-blue-60) r-sm bg-blue-10 fg-blue-80 transform:none animation-delay:600ms animation-duration:1s animation-fill-mode:both animation-name:layer-slide animation-timing-function:linear animation:none@print animation-play-state:paused@reduce-motion">→</span> </div></div>The starting keyframe depends on direction. For reverse and alternate-reverse, backwards fill uses the end of the keyframe list during the delay.
Reset fill behavior
Use none to return to ordinary styling during the 600ms delay and after the one-second active interval.
@theme { @keyframes layer-slide { from { transform: translateX(24px); } to { transform: translateX(96px); } }}<!-- None · base before and after --><div class="w:240px max-w:100%"> <p class="mb-sm font-mono font-xs text-muted">Base 0 · start 24 · end 96px</p> <div aria-hidden="true" class="h:56px p-xs outline:1px|dashed|var(--color-line-muted)"> <span id="target" class="grid place-items:center h:40px w:48px b:1px|solid|var(--color-blue-60) r-sm bg-blue-10 fg-blue-80 transform:none animation-delay:600ms animation-duration:1s animation-fill-mode:none animation-name:layer-slide animation-timing-function:linear animation:none@print animation-play-state:paused@reduce-motion">→</span> </div></div>The marker waits at 0px, jumps to its 24px starting keyframe when active, moves to 96px, then returns to 0px. Fill mode does not edit the underlying transform.
Apply conditionally
Resize before replaying. Below sm, both retains the starting and ending states; at sm, none leaves ordinary styling outside the active interval.
@theme { @keyframes layer-slide { from { transform: translateX(24px); } to { transform: translateX(96px); } }}<!-- Both → none at sm --><div class="w:240px max-w:100%"> <p class="mb-sm font-mono font-xs text-muted">Base 0 · start 24 · end 96px</p> <div aria-hidden="true" class="h:56px p-xs outline:1px|dashed|var(--color-line-muted)"> <span id="target" class="grid place-items:center h:40px w:48px b:1px|solid|var(--color-blue-60) r-sm bg-blue-10 fg-blue-80 transform:none animation-delay:600ms animation-duration:1s animation-fill-mode:both animation-name:layer-slide animation-timing-function:linear animation:none@print animation-play-state:paused@reduce-motion animation-fill-mode:none@sm">→</span> </div></div>Changing fill mode can update an already-finished animation’s visible result. Print removes the animation completely rather than retaining a motion snapshot.