Himanshu SharmaSoftware engineer
Let’s talk

CSS · Lesson 27 of 29

Animations

Create multi-step animations with CSS keyframes, control duration, iteration, direction and fill mode, build loaders and entrances, and honour reduced motion.

13 minPolishUpdated 18 October 2026

By the end of this lessonWrite a keyframe animation, control how it repeats and ends, and use animations tastefully with a reduced-motion alternative.

Beyond A to B

A transition goes from one state to another, and only when something triggers it. A keyframe animation can do more: it can start by itself, pass through several stages, loop and reverse. Spinners, pulsing notifications, text that slides in on load: all keyframe animations.

There are two parts: define the steps with @keyframes, and attach them with the animation property.

Step 1: @keyframes

css
@keyframes fade-up {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

You choose the name (fade-up). from and to are the start and end; for more steps, use percentages:

css
@keyframes pulse {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.15); }
  100% { transform: scale(1); }
}

Step 2: attach it

html
<style>
  @keyframes fade-up {
    from { opacity: 0; transform: translateY(20px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  @keyframes pulse {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.15); }
  }
  .enter { animation: fade-up 0.6s ease-out both; padding: 16px; background: #e0f2fe; border-radius: 8px; margin-bottom: 16px; }
  .dot   { width: 24px; height: 24px; border-radius: 50%; background: #ef4444; animation: pulse 1.2s ease-in-out infinite; }
</style>
<div class="enter">I fade up when the page loads.</div>
<div class="dot"></div>

(Open the editor's run button again to replay the entrance.) The shorthand is:

css
animation: name duration timing-function delay iteration-count direction fill-mode;

The animation properties

  • animation-name: which @keyframes.
  • animation-duration: how long one cycle takes (required).
  • animation-timing-function: pacing (ease, linear, ease-in-out, steps(8)).
  • animation-delay: wait before starting; negative values start part-way through, which is handy to offset a group of items.
  • animation-iteration-count: a number or infinite.
  • animation-direction: normal, reverse, alternate (forward then back), alternate-reverse.
  • animation-fill-mode: what the element looks like before and after:
    • none: returns to its normal styles afterwards.
    • forwards: keeps the final keyframe.
    • backwards: applies the first keyframe during the delay.
    • both: both of the above. Good for entrances.
  • animation-play-state: running or paused, which you can toggle on hover.

A loading spinner

html
<style>
  @keyframes spin { to { transform: rotate(360deg); } }
  .spinner {
    width: 40px; height: 40px;
    border: 5px solid #e2e8f0;
    border-top-color: #4f46e5;
    border-radius: 50%;
    animation: spin 0.9s linear infinite;
  }
</style>
<div class="spinner" role="status" aria-label="Loading"></div>

Spinners use linear timing so the speed stays constant. The role="status" and aria-label tell screen readers something is loading.

Staggered entrances

Delay each item a bit more than the last, using a custom property (the next lessons explain these) or separate rules:

css
.item { animation: fade-up .5s ease-out both; }
.item:nth-child(2) { animation-delay: .1s; }
.item:nth-child(3) { animation-delay: .2s; }

both makes each item stay invisible (first keyframe) during its delay, so nothing flashes in early.

Performance

As with transitions, animate transform and opacity. Animating width, height, top or box-shadow forces layout or paint work on every frame. For infinite animations, keep the animated area small and stop them when off-screen if you can. A hundred pulsing elements drain a phone battery.

Being kind: motion and accessibility

Animation can delight and can harm.

  • Reduced motion. Many people turn on "reduce motion" because large movement makes them dizzy or sick. Respect it:
css
@media (prefers-reduced-motion: reduce) {
  .enter, .dot, .spinner { animation: none; }
}

Better, swap movement for a simple fade rather than removing all feedback.

  • No flashing. Nothing should flash more than three times in a second; it can trigger seizures.
  • Do not auto-play distracting loops next to text people are trying to read, and offer a way to pause anything that moves for more than five seconds.
  • Do not hide content behind animation. If the animation fails, the content must still be visible.

Debugging

Browser developer tools have an Animations panel (Chrome/Edge) to slow down, replay and scrub animations. Slowing an animation to 10 percent speed reveals problems you would never see in real time.

When to use which

You want Use
A hover or focus effect Transition
A one-off entrance on load Animation with both
A loop (spinner, pulse) Animation with infinite
Scroll-linked effects Newer scroll-driven animation CSS (check browser support)
Complex sequences JavaScript or an animation library, only if CSS is not enough

Common mistakes

  • Forgetting animation-duration, so nothing plays (default is 0s).
  • A typo in the @keyframes name.
  • No fill-mode, so an element snaps back after finishing.
  • Animating layout properties.
  • No reduced-motion alternative.
  • Animations that never stop next to body text.

Exercise

Make a notification badge that pulses gently forever, and a card that slides in from the left on load and stays in place. Add reduced-motion protection for both.

Show one possible answer
html
<style>
  @keyframes slide-in { from { opacity: 0; transform: translateX(-30px); } to { opacity: 1; transform: none; } }
  @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.12); } }
  .card { animation: slide-in .5s ease-out both; padding: 16px; background: #dcfce7; border-radius: 10px; }
  .badge { display: inline-block; padding: 2px 10px; background: #dc2626; color: white; border-radius: 999px; animation: pulse 1.6s ease-in-out infinite; }
  @media (prefers-reduced-motion: reduce) { .card, .badge { animation: none; } }
</style>
<div class="card">New message <span class="badge">3</span></div>

Key points

  • A keyframe animation describes steps with @keyframes, then attaches to an element with the animation property.
  • Unlike a transition, an animation can run on its own, repeat, reverse and pass through several states.
  • animation-iteration-count, direction, timing-function and fill-mode control how it plays and what happens at the end.
  • Animate transform and opacity for smoothness and keep movement small and purposeful.
  • Always provide a prefers-reduced-motion alternative, and avoid anything that flashes.

Check yourself

1. What defines the steps of an animation?
2. What does animation-iteration-count: infinite do?
3. How do you keep an element in its final keyframe state after the animation ends?
4. Which is the safest animation for people sensitive to motion?

Free tools for this lesson