Course topics

By WebNest Studio Engineering Team

CSS Tutorial

CSS Animations and @keyframes

While transitions animate between exactly two states (a start and an end), CSS animations let you define multiple intermediate steps using the @keyframes rule, enabling far more complex, self-running motion — like a pulsing badge, a loading spinner, or a multi-stage entrance effect — without needing JavaScript or a triggering event like hover.

An animation has two parts: a @keyframes block that defines what changes at each stage of the animation as a percentage of its total duration, and a set of animation-* properties (or the animation shorthand) applied to the element that specify which keyframes to use and how to play them.

Why it matters

Loading spinners, pulsing notification badges and elements that fade in as a page appears are keyframe animations. Unlike a transition, they need no trigger and can loop for ever. Used sparingly they draw attention to what matters; overused they distract and can make some users unwell, so they come with a responsibility.

Defining @keyframes

A @keyframes rule is named and contains percentage-based stops describing the styles at each point in the animation's timeline: 0% is the start and 100% is the end, with any number of steps in between. You can also use the keywords from (equivalent to 0%) and to (equivalent to 100%) for simple two-stage animations.

The Animation Shorthand Properties

Once a @keyframes block exists, it's applied to an element via several properties, usually combined in the animation shorthand.

  • animation-name — the name of the @keyframes rule to use.
  • animation-duration — how long one full cycle takes, e.g. 2s.
  • animation-timing-function — the easing curve, same options as transitions (ease, linear, ease-in-out, etc.).
  • animation-iteration-count — how many times it repeats: a number, or infinite for a continuous loop.
  • animation-fill-mode — controls what styles apply before/after the animation plays, e.g. forwards keeps the final keyframe's styles after it ends.

Combining It All

The shorthand packs these together in order: animation: pulse 2s ease-in-out infinite; defines the name, duration, timing function, and iteration count in a single declaration. Additional values like a delay or fill-mode can be appended to the same shorthand.

Examples

A looping pulse animation for a notification badge

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

.badge {
  animation: pulse 1.5s ease-in-out infinite;
}
Output
The badge continuously grows slightly and fades a little at the halfway point of each 1.5-second cycle, then returns to its original size and opacity, repeating indefinitely.

A one-time entrance animation that keeps its final state

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

.hero-heading {
  animation: fade-in-up 0.6s ease-out forwards;
}
Output
The heading starts invisible and shifted 20px downward, then animates into full opacity and its natural position over 0.6 seconds, remaining in that final visible state afterward because of animation-fill-mode: forwards.

Common Mistakes

  • Forgetting animation-fill-mode: forwards on a one-time animation, causing the element to snap back to its pre-animation styles the instant the animation ends.
  • Using animation-iteration-count: infinite for effects that were only meant to play once, creating a distracting, never-ending motion.
  • Defining conflicting styles between the base selector and the 0% keyframe, leading to a visible flash before the animation begins.
  • Overusing multiple simultaneous infinite animations on a page, which can hurt performance and accessibility for users sensitive to motion.

Exercise

Define an animation named pulse that scales an element from its normal size to 110%. Apply it to the button so that it runs for 1 second with an ease-in-out timing, repeats for ever, and plays forwards then backwards.

Expected result: the button grows and shrinks smoothly and continuously.

CSS · starter code
/* The page contains: <main> with an <h1>, a <p> and a <button> */

/* TODO: @keyframes pulse, from scale(1) to scale(1.1) */

button {
  /* TODO: run pulse for 1s, ease-in-out, infinitely, alternating */
}
Show hints
  • Keyframes are defined with from and to, or with percentages.
  • The shorthand takes the values in this order: animation: pulse 1s ease-in-out infinite alternate.
Show solution
CSS · solution
/* The page contains: <main> with an <h1>, a <p> and a <button> */

@keyframes pulse {
  from {
    transform: scale(1);
  }
  to {
    transform: scale(1.1);
  }
}

button {
  animation: pulse 1s ease-in-out infinite alternate;
}

Quick quiz

1. Which at-rule defines the steps of an animation?

  • A. @animation
  • B. @steps
  • C. @transition
  • D. @keyframes
Show answer

D. The animation property then refers to the keyframes by name.

2. What does animation-fill-mode: forwards do?

  • A. Keeps the styles of the last keyframe after the animation ends
  • B. Plays the animation in reverse
  • C. Makes it loop
  • D. Delays the start
Show answer

A. Without it, the element returns to its original styles when the animation finishes.

3. Which value makes an animation repeat without end?

  • A. animation-iteration-count: 0
  • B. animation-iteration-count: infinite
  • C. animation-direction: loop
  • D. animation-duration: forever
Show answer

B. The default iteration count is 1.

Interview questions

What are the main animation properties?

Show answer

animation-name refers to the @keyframes rule. animation-duration is the length of one cycle. animation-timing-function is the acceleration curve. animation-delay is the wait before starting. animation-iteration-count is how many times it runs. animation-direction sets normal, reverse or alternate. animation-fill-mode decides which styles apply before and after. animation-play-state pauses and resumes. The animation shorthand sets them together.

How do you respect users who are sensitive to motion?

Show answer

Use the prefers-reduced-motion media query. Inside @media (prefers-reduced-motion: reduce), remove or greatly shorten animations and transitions that are not essential, and replace large movements with a simple fade. People with vestibular disorders can be made dizzy or nauseous by motion on screen, and this operating-system setting is how they ask sites to tone it down.

Key Points to Remember

  • @keyframes defines percentage-based stops describing an element's styles at each point in an animation timeline.
  • animation-name, animation-duration, animation-timing-function, and animation-iteration-count (often combined via the animation shorthand) control playback.
  • animation-fill-mode: forwards preserves the final keyframe's styles after a one-time animation completes.
  • Unlike transitions, animations can define many intermediate steps and can run automatically without a triggering event.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Shows a live preview of your HTML, CSS and JavaScript. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering