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, orallfor 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:
| Function | Effect |
|---|---|
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
- Slow it down. Change the card's transition duration to
0.6sand the spinner to2s. Feel how the timing changes the personality of the motion. - Swap the easing. Set the card transition to
ease-out, thenlinear, thenease-in-out. Notice how different they feel. - Add a fade-in. Write a
@keyframes fadeInthat goes fromopacity: 0; transform: translateY(20px);toopacity: 1; transform: translateY(0);and apply it to the card so it slides up when the page loads (noinfinitethis 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?
transition gradually animates a property from its old value to a new one over a set duration.
Which property scales an element without pushing other elements around?
transform: scale() changes the visual size without affecting the layout of surrounding elements.
What does @keyframes define?
@keyframes defines named steps (0% to 100%) that an animation moves through.
In transition: all 0.3s ease, what is 0.3s?
0.3s is the duration β how long the transition takes to complete.
Which properties are cheapest to animate smoothly?
transform and opacity are GPU-friendly and avoid expensive layout recalculation.
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.
Keep exploring
More in Coding