Transitions & Animations
Give your interfaces a pulse: learn how CSS transitions and keyframe animations add smooth, meaningful motion that guides attention without getting in the way.
A web page without motion is like a door that teleports open — technically functional, but a little startling. Motion, used well, tells a story: a button press feels responsive, a menu sliding in feels connected to where it came from, a loading spinner says "I'm working on it."
CSS gives you two tools for this: transitions for smooth changes between states (like hover-on and hover-off), and keyframe animations for choreographed sequences that can loop, reverse, or trigger on their own. By the end of this lesson you will be able to add motion that feels intentional rather than decorative — and you'll know exactly why some animations feel buttery-smooth while others stutter.
#Part 1: Transitions — Bridging Before and After
A transition interpolates a CSS property from one value to another whenever that property changes. Without a transition, changes are instant — a snap. With one, the browser draws every frame in between.
The shorthand takes four values: property, duration, easing, and delay. You can list multiple transitions separated by commas, giving each its own timing:
Easing controls the speed curve. The most useful built-in keywords:
| Keyword | Feel | |---|---| | ease | Starts fast, ends slow — the default | | ease-out | Fast start, gentle stop — great for elements entering the screen | | ease-in | Slow start, fast end — good for elements leaving | | linear | Constant speed — useful for spinners, mechanical elsewhere |
.button {
background-color: #3b82f6;
transform: scale(1);
/* property duration easing delay */
transition:
background-color 0.25s ease,
transform 0.2s ease-out 0s;
}
.button:hover {
background-color: #1d4ed8;
transform: scale(1.05);
}Transitions are like a dimmer switch, not a light switch
A regular light switch jumps instantly from off to on. A dimmer glides the light up over a second. CSS transitions are that dimmer — you set the two endpoints (original style and changed style) and the browser fills in every frame between them.
#Part 2: Transform — Move, Scale, Rotate Without Breaking Layout
transform is your most important animation companion. It visually moves, resizes, and rotates elements without affecting the document layout — the element still occupies its original space, only its painted position changes. The three functions you will reach for most are translate(), scale(), and rotate(). You can combine them in a single declaration:
.card:hover {
/* Lift the card 6px without pushing neighbours away */
transform: translateY(-6px) scale(1.02);
}
.icon.spinning {
transform: rotate(45deg);
}
/* Works great with a transition */
.card {
transition: transform 0.25s ease-out, box-shadow 0.25s ease-out;
}Always animate transform and opacity — not width, left, or top
When you animate width, height, top, or left, the browser must recalculate layout on every frame (a costly process called reflow). Animating transform and opacity skips reflow entirely — they are handled by the GPU. This is the single most impactful performance rule in CSS animation:
- Want something to move? Use
translate, notleft/top. - Want something to appear? Use
opacity, not togglingdisplay.
#Part 3: @keyframes — Scripting a Full Animation
@keyframes slide-in {
from { opacity: 0; transform: translateY(24px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
/* name dur easing fill-mode */
animation: slide-in 0.4s ease-out forwards;
}
/* Stagger three cards by delaying each */
.card:nth-child(2) { animation-delay: 0.1s; }
.card:nth-child(3) { animation-delay: 0.2s; }@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.08); opacity: 0.75; }
}
.badge {
animation: pulse 1.6s ease-in-out infinite;
}Always respect prefers-reduced-motion
Some users have vestibular disorders and have told their OS to reduce motion. One media query handles this for every animation on your page:
``css @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } } ``
This makes your site accessible to everyone and costs you nothing.
A teammate's hover animation is janky on their laptop. They're animating `left` and `width`. What is the best fix?
Key takeaways
- Use transition to smoothly animate between two CSS states; list the property, duration, and easing for each property you want to animate.
- Use @keyframes + animation for multi-step sequences, entrance effects, and loops — define percentage steps to control the timeline.
- Always animate transform and opacity instead of layout properties like width or left; they run on the GPU and never cause reflow.
- Easing is not decoration — ease-out for entering elements, ease-in for exiting, linear only for constant-speed loops.
- Wrap all motion in a prefers-reduced-motion media query so users who need a still experience always get one.
This hover animation is janky and stutters on a laptop. Based on the lesson's most important performance rule, what's wrong?
.card {
position: relative;
transition: left 0.25s ease-out, width 0.25s ease-out;
}
.card:hover {
left: -6px;
width: 102%;
}Complete this @keyframes entrance animation so the card fades and slides up into place. Fill in the property that moves and resizes an element without affecting layout.
@keyframes slide-in { from { opacity: 0; : translateY(24px); } to { opacity: 1; : translateY(0); } } .card { animation: slide-in 0.4s ease-out forwards; }
What visual result does this @keyframes animation produce on the .badge element?
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.08); opacity: 0.75; }
}
.badge {
animation: pulse 1.6s ease-in-out infinite;
}A user has enabled 'reduce motion' in their OS settings. Complete the media query feature name the lesson uses to detect this and switch off animations.
@media (prefers-reduced-motion: ) { .notification-dot { animation: none; } }
Create a .notification-dot element that pulses to draw attention. It should: (1) be a 14px circle in a vivid color of your choice, (2) use a @keyframes animation called 'ping' that scales it up to 1.6x and fades it out over 1 second, (3) loop infinitely. Bonus: stop the animation when the user prefers reduced motion.
Try it live — edit the code and hit Run to see it rendered: