✨
CodingπŸŽ“ Ages 14-18Intermediate 13 min read

CSS Transitions and Animations

Bring web pages to life with CSS: smooth hover transitions, the transition property, transform, and full @keyframes animations with timing and easing. Includes runnable demos, a try-it section and a quiz.

Key takeaways

  • transition smoothly animates a property change between two states
  • transform moves, scales or rotates an element without affecting layout
  • @keyframes defines a multi-step animation you can name and reuse
  • Timing functions like ease and ease-in-out shape how the motion feels
  • Animate cheap properties (transform, opacity) for smooth performance

Motion that means something

A static page is fine, but a touch of motion makes it feel alive and responsive: a button that gently lifts when you hover, a card that fades in, a loading spinner that turns. CSS gives you two tools for this β€” transitions for simple state changes and animations for richer, multi-step motion. Neither needs JavaScript. This lesson builds on Styling Web Pages with CSS, so you should be comfortable with selectors and the :hover pseudo-class.

Step 1: your first transition

Normally a CSS change happens instantly. Hover over a button and its colour snaps to the new value. A transition smooths that snap into a gradual change. You add it to the element's normal state and describe what to animate:

.button {
  background: #2563eb;
  transition: background 0.3s ease;   /* what, how long, how */
}
.button:hover {
  background: #1e40af;                 /* the new state */
}

The transition shorthand has three common parts:

  • property β€” what to animate (background, or all for everything).
  • duration β€” how long, e.g. 0.3s.
  • timing function β€” the feel of the motion, e.g. ease.

Now the colour eases smoothly over 0.3 seconds instead of snapping.

Step 2: transform β€” move without breaking layout

If you animate width, margin or top, the browser has to re-lay-out the whole page on every frame, which can stutter. The transform property changes how an element looks β€” its position, size or rotation β€” without disturbing anything around it. The main functions:

FunctionEffect
translateX(10px) / translateY(-4px)Slide horizontally / vertically
scale(1.1)Grow to 110%
rotate(15deg)Rotate

Combine transform with a transition for a polished hover lift:

.card {
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.card:hover {
  transform: translateY(-6px) scale(1.02);
  box-shadow: 0 12px 24px rgba(0,0,0,0.2);
}

The card rises a little and gains a shadow β€” a classic, professional effect in four lines.

Timing functions

The timing function controls the rhythm of the motion:

  • linear β€” constant speed (good for spinners).
  • ease β€” starts slow, speeds up, ends slow (the default, feels natural).
  • ease-in β€” starts slow.
  • ease-out β€” ends slow.
  • ease-in-out β€” slow at both ends.

For most UI interactions, ease or ease-out feel best.

Step 3: @keyframes animations

A transition needs a trigger and only goes from A to B. For motion that loops or has several steps, use @keyframes. First you define the named steps, then you attach them with the animation property:

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

.badge {
  animation: pulse 1.5s ease-in-out infinite;
}

The percentages are points along the timeline: 0% is the start, 100% the end. The animation shorthand here means: run the pulse keyframes, take 1.5s per cycle, ease in and out, and repeat forever (infinite). You can also use keywords from (= 0%) and to (= 100%) for simple two-step animations.

A complete worked example

Save this as motion.html and open it. Hover the card, watch the badge pulse, and see the box spin.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>CSS Motion Demo</title>
  <style>
    body { font-family: sans-serif; background: #0f172a; color: white;
           display: flex; gap: 32px; flex-wrap: wrap; padding: 40px; }

    /* 1. Hover transition on a card */
    .card {
      background: #1e293b; padding: 24px; border-radius: 12px; width: 180px;
      transition: transform 0.25s ease, box-shadow 0.25s ease;
    }
    .card:hover {
      transform: translateY(-8px) scale(1.03);
      box-shadow: 0 16px 30px rgba(0,0,0,0.4);
    }

    /* 2. Looping pulse animation */
    @keyframes pulse {
      0%, 100% { transform: scale(1);   }
      50%      { transform: scale(1.25); }
    }
    .badge {
      background: #f472b6; color: #0f172a; font-weight: bold;
      width: 60px; height: 60px; border-radius: 50%;
      display: flex; align-items: center; justify-content: center;
      animation: pulse 1.2s ease-in-out infinite;
    }

    /* 3. Continuous spin */
    @keyframes spin { to { transform: rotate(360deg); } }
    .spinner {
      width: 50px; height: 50px; border-radius: 50%;
      border: 6px solid #334155; border-top-color: #38bdf8;
      animation: spin 1s linear infinite;
    }
  </style>
</head>
<body>
  <div class="card"><h2>Hover me</h2><p>I lift and gain a shadow.</p></div>
  <div class="badge">NEW</div>
  <div class="spinner"></div>
</body>
</html>

Reading the result: the card uses a transition triggered by :hover. The badge and spinner use @keyframes animations that run on their own and loop forever. The spinner is just a circle with one coloured border edge, rotated continuously β€” a real loading indicator you could drop into any project.

Try it yourself

  1. Slow it down. Change the card's transition duration to 0.6s and the spinner to 2s. Feel how the timing changes the personality of the motion.
  2. Swap the easing. Set the card transition to ease-out, then linear, then ease-in-out. Notice how different they feel.
  3. Add a fade-in. Write a @keyframes fadeIn that goes from opacity: 0; transform: translateY(20px); to opacity: 1; transform: translateY(0); and apply it to the card so it slides up when the page loads (no infinite this time).

Challenge: Build a "like" button that, when you add a .liked class to it, transitions its colour and does a quick pulse scale. To toggle the class on click you'll need a line of JavaScript β€” see JavaScript Events and Clicks for how to listen for a click and call classList.toggle('liked').

Quick quiz

Test yourself and earn XP

What does the transition property do?

Which property scales an element without pushing other elements around?

What does @keyframes define?

In transition: all 0.3s ease, what is 0.3s?

Which properties are cheapest to animate smoothly?

FAQ

A transition animates a single change from state A to state B, usually triggered by something like :hover or a class change β€” it needs a trigger. A @keyframes animation can run on its own, loop, have many steps, and play as soon as the page loads. Use transitions for simple hover effects; use animations for looping or multi-step motion.

Usually because you're animating layout properties like width, height, top or margin, which force the browser to recalculate the page on every frame. Animate transform and opacity instead β€” they're handled by the GPU and stay smooth. Also keep durations short (0.2s–0.5s) for interactions.