CSS animations explained: keyframes, timing and reduced motion
CSS can move things without any JavaScript. This guide explains the pieces and the habits that keep animations smooth and respectful.
Transition or animation?
A transition changes a property from one value to another when something happens, such as a hover. An animation runs by itself through several steps called keyframes, can repeat, and does not need a trigger. Use a transition for state changes (transition: transform 0.2s ease) and an animation for loops, entrances and attention effects.
Keyframes
@keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-30px); } } defines the steps. Percentages are points in time of one run. from and to are the same as 0% and 100%. Then an element uses it: animation: bounce 1.2s ease-in-out infinite;.
The shorthand in order
animation: name duration timing-function delay iteration-count direction fill-mode. Two numbers with units (times) appear: the first is the duration, the second the delay. The generator writes them in this order.
Timing functions
linear is constant speed. ease (the default) starts quickly and ends slowly. ease-in-out is slow at both ends. cubic-bezier(a, b, c, d) is any custom curve; values above 1 overshoot, which gives a springy feel. steps(n) jumps, useful for sprite sheets and typing effects.
Fill mode
By default an element goes back to its normal style after the animation ends. forwards keeps the last keyframe, backwards applies the first keyframe during the delay, and both does both. For entrance effects, both prevents a flash of the final state before the animation starts.
Keep it smooth
The browser can animate transform and opacity on the graphics card without recalculating the page layout. Animating width, height, top, left or margin forces layout work for every frame, which stutters on phones. Move things with translate, resize with scale.
Reduced motion
Some people get dizzy or distracted by motion, and they turn on "reduce motion" in their system. Respect it:
@media (prefers-reduced-motion: reduce) {
.element { animation: none; }
}
Keep the content visible in that case (for example, an element that fades in must still show).
Good habits
- Short for interface feedback (0.15 to 0.4 seconds), longer for scenery.
- Never loop a movement near text people are reading.
- Do not rely on motion alone to give information.
- Pause infinite animations that are off screen if they are heavy.
Why does my animation run only once?
It ran, ended, and the browser does not restart it unless the animation name changes or the class is removed and added again.
How do I run something after the animation ends?
Listen for the animationend event in JavaScript.
This is an independent site. It is not connected with the W3C, MDN, Google, Apple, Mozilla or Microsoft. Browser and product names belong to their owners.