CSS Tutorial / CSS Keyframe Animations, Fill Modes, and Pausing
CSS keyframe animations describe property values over an animation's timeline and can run without a hover or focus trigger. Duration, delay, iteration count, fill mode, and play state control different parts of that behavior.
Distinguish transitions and keyframe animations
| standard | transition | animation |
|---|---|---|
| start condition | When the value changes due to hover, focus, class change, etc. | After the animation is applied and the delay time has passed, |
| state definition | CSS values before and after change | Specify start, middle, and end values in keyframes |
| repetition and direction | Proceed with state changes | Separately specify the number of times, direction, and stop status |
Transition is simple as long as the background color of the button changes. You can look at animations for effects such as when a notification first appears or for a visual explanation that goes through several steps. Controlling the same property on the same element with two functions simultaneously makes it difficult to keep track of which value is applied, so for this exercise we split the roles. If the effects before and after the state change are sufficient, consider starting with a simple transition using the CSS Transforms and Transitions of the transition.
Define keyframes and playback settings
In the keyframe, from corresponds to 0%, and to corresponds to 100%. If you need an intermediate state, add a progress percentage like 50%. Just declaring a keyframe name will not cause it to play; you must associate it with the animation-name of the element.
@keyframes travel {
0% { transform: translateX(0); }
50% { transform: translateX(3rem); }
100% { transform: translateX(6rem); }
}
.marker { animation: travel 4s linear 1s 1 normal forwards; }
This partial code is a shortened version of the keyframe and .marker setting of the same name in the full example below. 4s is the time it takes to play once and 1s is the startup delay. If the duration is not specified, the default value is 0 seconds, making it difficult to observe the moving process. You can reduce confusion about time and frequency by dividing it into longhand properties at first.
| Property | Values for this example | Things to change |
|---|---|---|
animation-duration |
4s |
one playing time |
animation-timing-function |
linear |
Difference between constant progress and ease curve |
animation-delay |
1s |
Waiting time before starting |
animation-iteration-count |
1 |
number of plays; infinite repeats infinitely |
animation-direction |
normal |
Forward/reverse/alternate playback |
animation-fill-mode |
forwards |
Value to apply during out-of-play time |
animation-play-state |
Default running |
Pause current progress with paused |
Even if the rates are written at equal intervals, the actual change in position will vary as a function of time. This exercise uses linear, which makes it easy to check intermediate positions. For keyframe creation and connection, refer to MDN @keyframes document.
Compare direction, iteration count, and fill mode
normal proceeds from the beginning to the end, and reverse proceeds from the end to the beginning. alternate plays alternately in forward direction the first time and reverse direction the next time. If the number of iterations is 2 and alternate, the last one will arrive at the first keyframe.
forwards is not a setting that always maintains 100% keyframes. Depending on the playback direction and number of times, the value of the last actually reached keyframe is maintained. In the example, once in the forward direction maintains the 6rem position, once in the reverse direction it maintains the 0 position, and twice in the alternate direction also maintains the 0 position. This description is based on an integer number of completed plays, a fractional number may end in mid-progress. MDN fill-mode description Conditions for each direction are summarized in.
| fill-mode | Delaying | After finishing |
|---|---|---|
none |
Basic CSS | Basic CSS |
forwards |
Basic CSS | Last played keyframe |
backwards |
Keyframe to play first | Basic CSS |
both |
Keyframe to play first | Last played keyframe |
If the base CSS value and end keyframe value are the same, the difference between none and forwards may be invisible. The movement box below has a default position of 1rem, a start of 0, and an end of 6rem to reveal the difference. On the other hand, the must-read guide defaults to the completion state that appears when the animation is removed.
Run and change one setting at a time
Save the following code as animation-lab.html in UTF-8 and open it in your browser. The upper guidance appears briefly, and the lower decorative box starts moving after 1 second and moves for 4 seconds. After modifying the values, save the file and reload the page to compare from the beginning.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Keyframes and end states exercise</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
.notice { padding: 1rem; border: 1px solid #90bdd8; border-radius: .5rem; background: #eaf4fa;
opacity: 1; transform: none; animation: appear 400ms ease-out; }
@keyframes appear {
from { opacity: 0; transform: translateY(.5rem); }
to { opacity: 1; transform: translateY(0); }
}
.track { margin-block: 1rem; padding: 1rem; border: 2px dashed #8a9ba7; }
.marker { width: 2rem; height: 2rem; border-radius: .25rem; background: #166b92;
transform: translateX(1rem);
animation-name: travel; animation-duration: 4s; animation-timing-function: linear;
animation-delay: 1s; animation-iteration-count: 1; animation-direction: normal;
animation-fill-mode: forwards; }
@keyframes travel {
0% { transform: translateX(0); background-color: #166b92; }
50% { transform: translateX(3rem); background-color: #6a3989; }
100% { transform: translateX(6rem); background-color: #28633b; }
}
#pause:checked ~ .track .marker { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
.notice, .marker { animation: none; }
}
</style>
</head>
<body>
<main>
<h1>Check entrance and movement</h1>
<p class="notice">This notice remains readable without animation.</p>
<h2>Comparing end states</h2>
<p>Movement starts one second after loading. Refresh the page to compare again.</p>
<input id="pause" type="checkbox"><label for="pause">Pause movement</label>
<div class="track"><div class="marker" aria-hidden="true"></div></div>
<p>The colors and movement are decorative. Essential information remains available independently of the animation, as in this sentence.</p>
</main>
</body>
</html>
- In the default settings, the box is at position 1rem during delay, moves to 0 at start, and remains at position 6rem when it ends. Check the difference between the delay value and the start value.
- Change only
animation-fill-modetononeand refresh. Once finished, it returns to the default position of 1rem and the default blue color. Restore back to forwards. - Change only fill-mode to
both. Starting position 0 applies even during delays. After confirmation, return to forwards. - Leave the count as 1 and change the direction to
reverse. After completion, check that the 0 position remains and restore to normal. - Check the results by changing the direction to alternate and keeping the count at 1. In that state, if you change the number to 2, a second playback will occur. After confirmation, the count is 1 and the direction is restored to normal.
- Immediately after refreshing, turn on the Pause checkbox and wait a moment. If you cancel it, you will continue from where you left off. This is different from the ability to replay from the beginning.
Keep content visible with reduced motion
The reduced setting removes the animation of the guidance and decoration boxes. The instructions are still readable as the default is opacity: 1 and the box appears at the default position of 1rem. Leaving readable state only on end keyframes or forwards can cause content to disappear in the absence of animation.
Enable reduced motion in the DevTools Rendering panel or operating system and reload. Both elements should remain still while their content stays visible. Consult the MDN prefers-reduced-motion guide, then restore the emulation setting after the comparison. Also follow the Check Responsive CSS with Width, Zoom, and Keyboard Tests to check clipping and usability with motion disabled.
Before adding infinite repetition, decide why motion is needed and how it will stop. This example uses finite iterations and supplies important information independently of movement and color. If nothing moves, check the animation name, duration, and reduced-motion condition. For an unexpected end state, compare direction, iteration count, fill mode, and the underlying CSS.










