CSS can do far more than style static elements. It can move them, fade them, scale them, and loop them, all without a single line of JavaScript. The tool for this is the keyframe animation. Where a CSS transition animates an element between two states, like a button changing colour on hover, keyframes let you define a whole sequence of steps, giving you full control over what happens at every point in the animation.
The Basic Idea
A keyframe animation has two parts. First you define the animation itself with an @keyframes rule, giving it a name and describing how it should look at the start and the end. Then you apply that named animation to an element using the animation property.
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; }}.box { animation: fadeIn 2s ease-in-out;}
The @keyframes fadeIn block says the element starts fully transparent and ends fully opaque. The .box rule then applies that animation, telling it to run over two seconds with an ease-in-out speed curve. The result is a box that gently fades into view. The from and to keywords are simply shorthand for the beginning and end of the animation.
Adding More Steps
from and to only give you two points. For anything more complex, you use percentages instead, where 0% is the start and 100% is the end. This lets you define as many intermediate steps as you need.
@keyframes bounce { 0% { transform: translateY(0); } 50% { transform: translateY(-20px); } 100% { transform: translateY(0); }}.ball { animation: bounce 1s ease-in-out infinite;}
Here the element starts at its normal position, rises 20 pixels at the halfway mark, then returns to where it began. Because the animation moves up and back down within a single cycle, and infinite makes it repeat forever, the result is a continuous bouncing effect. The percentage approach is what makes keyframes so flexible: you can choreograph an element through any number of stages.
Understanding the Animation Properties
The animation shorthand bundles together several individual properties. It is worth knowing what each one controls, because you will often want to set them deliberately.
The animation-name is the name of the @keyframes rule to use. The animation-duration sets how long one cycle takes, written in seconds or milliseconds like 2s or 500ms. The animation-timing-function controls the speed curve, with values like ease, linear, and ease-in-out changing how the animation accelerates and decelerates. The animation-delay holds the animation back before it starts. The animation-iteration-count decides how many times it runs. The animation-direction controls whether it plays forwards, in reverse, or alternates back and forth. And the animation-fill-modedetermines what state the element rests in once the animation finishes.
Controlling How Many Times It Runs
The iteration count is one of the most useful properties to understand. By default an animation runs once, but you can give it any number:
.box { animation: fadeIn 2s ease-in 3;}
This plays the animation three times and then stops. When you want something to loop endlessly, like a loading spinner or a pulsing highlight, use infinite:
.box { animation: fadeIn 2s ease-in infinite;}
Keeping the Final State
A common surprise for people new to animations is that, by default, an element snaps back to its original styling the moment the animation ends. The animation-fill-mode property controls this behaviour.
The default value, none, resets the element to its original state once the animation finishes. forwards keeps the element at its final keyframe, which is usually what you want when animating something into a new position or size. backwards applies the first keyframe’s styling during any delay before the animation starts. And both combines the forwards and backwards behaviours.
@keyframes grow { from { transform: scale(1); } to { transform: scale(1.5); }}.box { animation: grow 2s ease-in forwards;}
Because of forwards, the box scales up to 1.5 times its size and stays there, rather than snapping back to its original size when the animation ends. Without forwards, the growth would happen and then instantly undo itself, which is rarely the intended effect.
Using the Shorthand
Writing out every property separately is verbose:
.box { animation-name: fadeIn; animation-duration: 2s; animation-timing-function: ease-in-out; animation-delay: 1s; animation-iteration-count: infinite; animation-direction: alternate;}
The animation shorthand lets you collapse all of that into a single line:
.box { animation: fadeIn 2s ease-in-out 1s infinite alternate;}
The values are read in order: name, duration, timing function, delay, iteration count, and direction. The shorthand is the form you will see most often in real code, though the order can take a little getting used to. The one thing to watch is that the first time value is read as the duration and the second as the delay, so if you include both, keep them in that sequence.
A Practical Example
Keyframe animations are not just for decoration. A subtle pulse on a button, triggered on hover, draws attention to the most important action on a page:
@keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); }}.button:hover { animation: pulse 0.5s ease-in-out;}
When the user hovers, the button grows slightly to 1.1 times its size at the midpoint, then settles back to normal. The whole thing takes half a second and gives a satisfying, responsive feel without being distracting. Pairing an animation with a hover state like this is one of the most common and effective uses of keyframes in real interfaces.
When to Use Keyframes, and When Not To
Keyframe animations are the right tool when you need multiple steps, looping, or fine control over the sequence of an animation. A bouncing ball, a spinning loader, a multi-stage attention effect, these all need keyframes.
For the simplest cases, though, a transition is often the better choice. If all you want is for something to smoothly change between two states, like a colour shift or a fade on hover, transition is lighter and simpler. Reach for keyframes when two states are not enough.
The Takeaway
CSS keyframe animations give you JavaScript-free motion with full control over every stage. Define the steps with @keyframes, apply them with the animation property, and tune the behaviour with duration, timing, delay, and iteration count. Remember animation-fill-mode: forwards when you want the element to stay at its final state, and reach for transition instead when a simple two-state change is all you need. With these pieces, you can add polish and personality to an interface using nothing but CSS.
See you soon
[…] is worth being clear about where transitions fit. A transition animates a property between two states, a start and an end, and it needs a […]
[…] Keyframe Animations: https://datalad.co.uk/bringing-pages-to-life-with-css-keyframe-animations/ […]