Transitions animate from state A to state B when something changes. Animations go further. They can start on their own, loop forever and pass through many steps along the way. Loading spinners, pulsing notification dots, bouncing arrows and fade-in effects are all CSS animations.
Animations need two pieces: a set of keyframes that describes the motion, and an animation property that applies those keyframes to an element. Keeping the two separate means you can define a motion once and reuse it on many elements with different speeds and delays.
Step 1: define the keyframes
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.15); }
100% { transform: scale(1); }
}@keyframes gives the animation a name and describes how the element should look at points along its timeline. from and to are shortcuts for 0% and 100%.
If you leave out the 0% keyframe, the animation starts from the element's current style, which is handy for simple fade-ins.
Step 2: apply the animation
.dot { animation: pulse 1.5s ease-in-out infinite; }| Property | Example | Meaning |
|---|---|---|
animation-name | pulse | Which keyframes to use |
animation-duration | 1.5s | Length of one cycle |
animation-timing-function | ease-in-out, linear, steps(8) | The speed curve |
animation-delay | 0.2s | How long to wait before starting |
animation-iteration-count | 3, infinite | How many times to play |
animation-direction | alternate, reverse | Play backwards on alternate cycles |
animation-fill-mode | forwards, both | Keep the final state after finishing |
animation-play-state | paused | Pause the animation |
Without animation-fill-mode: forwards, an element jumps back to its original style when a one-off animation ends. That's the most common surprise with entrance effects.
Staggering animations
Give several elements the same animation with increasing animation-delay values and they move one after another, like a wave or a "someone is typing" indicator. A delay of 0.1 to 0.2 seconds between items is usually enough to create the effect without feeling slow.
When to use an animation
Good animations have a job. A spinner says "loading", a pulse draws attention to a new message, and a short entrance effect shows where new content appeared. Constant decorative movement, on the other hand, distracts from the content and drains phone batteries. Animate transform and opacity where possible, keep durations short, and ask whether each animation helps the visitor.
CSS Animations Syntax
Animation syntax
@keyframes name {
from { opacity: 0; }
to { opacity: 1; }
}
.el { animation: name 0.5s ease-out 0s 1 forwards; }CSS Animations Summary
@keyframesdefines the steps of an animation and gives it a name.- The
animationproperty applies keyframes with a duration, timing and repeat count. animation-fill-mode: forwardskeeps the final state after the animation ends.- Increasing delays create staggered, wave-like effects.
- Respect
prefers-reduced-motionand give every animation a purpose.
CSS Animations Examples
Each example runs in the page. Edit the code and press Run to see what changes.
A loading spinner
<div class="spinner" role="status" aria-label="Loading"></div>.spinner {
width: 48px;
height: 48px;
border: 5px solid #e1e6ff;
border-top-color: #4467ff;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}Line-by-line explanation
- Lines 4 to 6A circle with one coloured side.
- Line 7Run "spin" every 0.8s, at constant speed, forever.
- Lines 10 to 12Only the end state is needed. It starts from the current rotation (0deg).
Typing indicator with staggered delays
<div class="typing"><span></span><span></span><span></span></div>.typing { display: flex; gap: 6px; padding: 14px 18px; background: #f3ede2; border-radius: 20px; width: fit-content; }
.typing span {
width: 10px;
height: 10px;
border-radius: 50%;
background: #6b6d79;
animation: bounce 1s ease-in-out infinite;
}
.typing span:nth-child(2) { animation-delay: 0.15s; }
.typing span:nth-child(3) { animation-delay: 0.3s; }
@keyframes bounce {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-8px); }
}Line-by-line explanation
- Line 8All three dots share one animation.
- Lines 11 to 12Each dot starts a little later, creating the classic "someone is typing" wave.
- Line 15Several percentages can share one keyframe.
Fade-in entrance that stays
Press Run again to replay it.
<div class="toast">✅ Saved successfully</div>.toast {
display: inline-block;
padding: 12px 18px;
background: #15161b;
color: white;
border-radius: 12px;
font-family: sans-serif;
opacity: 0;
animation: slide-in 0.5s ease-out 0.3s forwards;
}
@keyframes slide-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}Line-by-line explanation
- Line 8Starts invisible.
- Line 9Wait 0.3s, animate for 0.5s, and
forwardskeeps the final (visible) state.
Common CSS Animations Mistakes
Defining keyframes but never applying them
@keyframes wiggle { 50% { rotate: 10deg; } }
.bell { animation-duration: 1s; }@keyframes wiggle { 50% { rotate: 10deg; } }
.bell { animation: wiggle 1s infinite; }An animation needs at least a name and a duration.
The animation jumps back at the end
.box { animation: grow 1s; }
@keyframes grow { to { width: 300px; } }.box { animation: grow 1s forwards; }
@keyframes grow { to { width: 300px; } }Without forwards, the element returns to its original styles when the animation ends.
CSS Animations in Real Projects
Animation in products
Skeleton loading screens that shimmer, notification badges that pulse, onboarding illustrations, "scroll down" arrows and success ticks that draw themselves are often pure CSS animations. Good teams keep them short and purposeful: motion should explain what's happening, not distract.
CSS Animations Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Apply the "spin" animation for 2 seconds, forever.
.logo {
animation: 2s linear ;
}
Which at-rule defines an animation's steps?
@keyframes name { … }.Which value keeps the final keyframe's styles after the animation ends?
fill-mode: forwards holds the end state.How many times does the bell ring?
.bell { animation: ring 0.5s ease 3 alternate; }
alternate only changes direction on each cycle.The heart should pulse forever, but nothing moves. Fix the CSS (keep the name "beat").
<div class="heart">❤️</div>.heart {
display: inline-block;
font-size: 40px;
animation: beat;
}
@keyframe beat {
50% { transform: scale(1.3); }
}- Animation name is beat
- Has a duration
- Loops forever
- @keyframes beat is defined
CSS Animations Quiz
3 questions to check what you learned.
What does
infinitemean in an animation?- It repeats forever
- Very slow
- It never starts
- It runs once
What's the main difference between a transition and an animation?
- Animations can't use transform
- Animations can run on their own, loop and have many steps; transitions only animate a change between two states
- Transitions are newer
- There is no difference
How do you make several elements animate one after another?
- Use different keyframes for each
- Use
animation-order - Give each a slightly larger
animation-delay - It's impossible in CSS
CSS Animations Challenge
Pulsing notification dot
Make .dot pulse:
- Define
@keyframes pulsethat scales to1.4and fades toopacity: 0.5at50% - Apply it with a duration of
1.2s,ease-in-out,infinite
<button class="bell">🔔 <span class="dot"></span></button>.bell { position: relative; font-size: 28px; background: none; border: 0; }
.dot { position: absolute; top: 2px; right: 0; width: 10px; height: 10px; border-radius: 50%; background: #e5484d; }
- Keyframes "pulse" exist with scale and opacity at 50%
- Dot uses pulse
- 1.2s duration
- ease-in-out timing
- Loops forever