You’ve mastered CSS hover
effects and transitions to add that spark of interactivity to your sites. But what if you want to create more complex, multi-step animations that run without any user interaction? What if you want to make an element pulse, slide in, or shake on command?
For that, you need to unlock the power of CSS keyframes
.
In this deep dive, we’ll go from the basic syntax to building three different, practical animations that you can use in your projects today.
What are @keyframes
?
Think of keyframes
as the director of your animation.
In a movie, the director sets key moments (frames) for an actor: „Start here, walk to the door, and then raise your hand.“ The actor then smoothly fills in the motion between those key points.
CSS @keyframes
work the same way. You define the key styles at specific points during the animation timeline, and the browser handles the smooth transition between them.
The @keyframes
rule is composed of two main parts:
- The Name: You give your animation a unique name, like
slideInFromLeft
orpulse
. We’ll use this name later to apply the animation to an element. - The Animation Timeline: You define the „stops“ or key moments of your animation. These can be defined using
from
andto
for simple two-step animations, or with percentages (0%
,25%
,100%
) for more complex sequences.
Here’s the basic structure:
@keyframes your-animation-name {
from { /* CSS styles at the start (0%) */ }
to { /* CSS styles at the end (100%) */ }
}
/* Or for more steps */
@keyframes another-animation-name {
0% { /* Styles at the beginning */ }
50% { /* Styles in the middle */ }
100% { /* Styles at the end */ }
}
The animation
Property: Bringing it to Life
Defining a @keyframes
rule doesn’t do anything on its own. You need to apply it to an element using the animation
property.
The animation
property is a shorthand that combines several sub-properties:
-
animation-name
: The name of your@keyframes
rule. -
animation-duration
: How long the animation should take (e.g.,3s
,500ms
). -
animation-timing-function
: The „speed curve“ of the animation (e.g.,ease-in
,linear
,ease-in-out
). This controls the acceleration and deceleration. -
animation-delay
: A pause before the animation starts (e.g.,1s
). -
animation-iteration-count
: How many times to repeat the animation (e.g.,3
, orinfinite
for non-stop). -
animation-direction
: The direction of the animation on repeat (e.g.,normal
,reverse
,alternate
).alternate
is fantastic for looping animations, as it smoothly goes back and forth. -
animation-fill-mode
: What styles apply before the animation starts or after it ends (e.g.,forwards
to keep the styles from the last keyframe).
You can write them all out individually, but it’s cleaner to use the shorthand:
.my-element {
/* animation: name duration timing-function delay iteration-count direction fill-mode; */
animation: slideInFromLeft 1s ease-out 0s 1 normal forwards;
}
Now, let’s build something!
Example 1: The Pulsing Notification Dot
This is a classic UI element used to draw attention to something new. We want a dot that gently fades and scales up and down forever. This requires a from
/to
or 0%
/100%
sequence. Using alternate
direction will make it look smooth and natural.
The HTML:
<div class="notification">
<p>New Message</p>
<div class="dot"></div>
</div>
The CSS:
/* First, let's define the animation itself */
@keyframes pulse {
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(1.5);
opacity: 0.5;
}
}
/* Now, let's style the dot and apply the animation */
.dot {
width: 10px;
height: 10px;
background-color: #ff4757; /* A bright red */
border-radius: 50%;
/* Apply the animation! */
animation-name: pulse;
animation-duration: 800ms;
animation-iteration-count: infinite;
animation-direction: alternate; /* This makes it go back and forth smoothly */
animation-timing-function: ease-in-out;
}
/* Some basic styling for the container */
.notification {
display: flex;
align-items: center;
gap: 10px;
font-family: sans-serif;
}
Why this works: The pulse
animation scales the dot from its normal size (scale(1)
) to 1.5 times its size while also fading its opacity. Because we set the animation-direction
to alternate
, it plays forwards (100%) and then backwards (0%), creating a seamless, gentle throbbing effect.
Example 2: The „Shake on Error“ Input Field
Imagine a user enters the wrong password. A subtle shake is a great visual cue. We’ll use a multi-step keyframe to create a quick side-to-side wobble.
The HTML:
<input class="input-field" type="password" placeholder="Password">
We’ll add the .shake
class with JavaScript when an error occurs, but for this demo, we’ll add it directly in the HTML.
<input class="input-field shake" type="password" placeholder="Password">
The CSS:
@keyframes shake {
0% { transform: translateX(0); }
25% { transform: translateX(-5px); } /* Move left */
50% { transform: translateX(5px); } /* Move right */
75% { transform: translateX(-5px); } /* Move left */
100% { transform: translateX(0); }
}
.input-field {
padding: 10px;
border: 2px solid #ccc;
border-radius: 4px;
}
.input-field.shake {
/* Apply the animation when the .shake class is present */
animation: shake 400ms ease-in-out;
}
Why this works: We’ve defined keyframes at 0%
, 25%
, 50%
, and 75%
to create a back-and-forth motion with translateX
. It starts at its original position, moves left, then right, then left again, and finally returns to the center. This all happens over a brisk 400 milliseconds, resulting in a sharp, noticeable shake.
Example 3: Slide-in Page Loader
Let’s make an elegant entry animation for an element, like a hero title or a section of content. We want it to slide in from the bottom and fade in at the same time.
Here, the animation-fill-mode: forwards
property is crucial. It ensures the element stays at its final state (opacity: 1
and transform: translateY(0)
) after the animation is done.
The HTML:
<h1 class="title">Welcome to the Page</h1>
The CSS:
@keyframes slideInFromBottom {
from {
opacity: 0;
transform: translateY(50px); /* Start 50px below its final position */
}
to {
opacity: 1;
transform: translateY(0); /* End at its natural position */
}
}
.title {
font-family: sans-serif;
font-size: 3rem;
/* Start the element as invisible before the animation begins */
opacity: 0;
/*
Shorthand: name duration timing-function fill-mode
*/
animation: slideInFromBottom 1s ease-out forwards;
}
Why this works: The element starts off invisible (opacity: 0
) and 50px below its final position. The animation then moves it up into place while simultaneously fading it in. The magic of forwards
is that without it, the element would snap back to being invisible and 50px down after the animation finished. forwards
tells it to hold the styles defined in the to
keyframe.
Conclusion: Your Turn to Animate!
You’ve now seen how @keyframes
and the animation
property work together to create everything from subtle, infinite loops to sharp, one-off effects.
The key takeaways are:
- Define with
@keyframes
: Give your animation a name and define the key style changes over a timeline using percentages. - Apply with
animation
: Use theanimation
shorthand property to assign your named animation to an element and control its duration, timing, and repetition. - Don’t forget the details: Properties like
animation-direction: alternate;
andanimation-fill-mode: forwards;
are the secret ingredients that can make an animation feel polished and professional.
The best way to learn is by doing. Try creating a fade-in animation, a spinning loader, or a bounce effect. Experiment with different timing functions and durations. The web is your playground! Happy animating.