Why motion matters
Motion is communication. A button that eases into its pressed state, a panel that slides into view and a list that reorders smoothly all tell the user what just happened. Done well, motion makes an interface feel responsive and polished. Done badly, it distracts and frustrates.
CSS gives you three tools: transitions for state changes, keyframes for sequences and transforms for movement. Master those and you rarely need JavaScript for everyday animation.
Transitions
A transition smoothly interpolates between two states and runs when a property changes.
/* button.css */
.button {
background: #2563eb;
transform: translateZ(0);
transition: background-color 150ms ease, transform 150ms ease;
}
.button:hover {
background: #3b82f6;
transform: translateY(-1px);
}
.button:active {
transform: translateY(0);
}
The shorthand takes the property, duration, timing function and optional delay. Transitions need a trigger: :hover, :focus, :checked, a class change from JavaScript or a media query. Keep durations short — 150 to 300 milliseconds is usually enough for feedback.
Transforms
Transforms change how an element looks without affecting the layout of others, which is why they are cheap to animate.
/* transform.css */
.move { transform: translateX(24px); }
.grow { transform: scale(1.1); }
.turn { transform: rotate(45deg); }
.skew { transform: skewX(-10deg); }
.combo {
transform: translateY(-4px) scale(1.02) rotate(-1deg);
}
translate, scale and rotate can run on the compositor, so they stay smooth even under load. Animating width, height, top or margin instead forces the browser to recalculate layout on every frame.
Keyframes
For multi-step or looping animation, use @keyframes and animation.
/* spinner.css */
@keyframes spin {
to { transform: rotate(360deg); }
}
.spinner {
animation: spin 800ms linear infinite;
}
@keyframes fade-up {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.enter {
animation: fade-up 300ms ease-out both;
}
The animation shorthand sets name, duration, timing, delay, iteration count, direction and fill mode. both is handy for entrance animations because it applies the first and last keyframe states.
Timing functions
The timing function controls the feel. Easing is what makes motion look natural rather than mechanical.
/* easing.css */
.linear { transition-timing-function: linear; }
.ease-out { transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1); }
.bounce { transition-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55); }
- ease-out for elements entering the screen, so they decelerate.
- ease-in for elements leaving.
- ease-in-out for movement between two visible states.
- steps() for frame-like effects such as typing or sprite animation.
Performance
The rule is simple: animate transform and opacity. Those can be handled by the compositor, skipping layout and paint. Anything that changes geometry triggers layout, and anything that changes colour or shadow triggers paint.
/* hint.css */
.animated {
will-change: transform;
}
will-change hints that an element is about to animate, so the browser can promote it to its own layer. Use it sparingly and remove it once the animation finishes; too many promoted layers waste memory. See the Browsers & Rendering guide for why.
Accessibility and reduced motion
Some users experience dizziness or nausea from motion, and the operating system exposes that preference.
/* reduced.css */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
A blanket rule like this is a safe baseline. For a more nuanced approach, replace large movement with a simple fade. Never convey meaning through motion alone.
View transitions
The View Transitions API animates between two DOM states or pages natively.
// navigate.js
document.startViewTransition(() => {
updateTheDOM();
});
The browser snapshots the old and new states and animates between them, and you control the effect with CSS pseudo-elements. It is ideal for page navigation and list reordering. Provide a plain fallback for browsers that do not support it.
Patterns
- Hover and focus feedback. Short transitions on colour and transform.
- Entrance animations. Fade and slide in with
bothfill mode. - Staggered lists. Delay each item slightly, often set with a CSS variable and inline style.
- Skeletons and spinners. Looping keyframes to signal loading.
- Accordions and drawers. Transition height via
grid-template-rowsfrom0frto1fr, which animates cleanly.
Best practices
- Animate transform and opacity; avoid layout properties.
- Keep durations short: 150–300ms for feedback, longer for emphasis.
- Use ease-out for entrances and ease-in for exits.
- Respect prefers-reduced-motion.
- Use
will-changesparingly and clean up after animating. - Prefer CSS for simple cases and JavaScript for complex sequences.
- Test on low-powered devices, not just a fast desktop.
Common mistakes
- Animating width, height or top and causing jank.
- Leaving
will-changeon permanently and wasting memory. - Long, slow animations that make the UI feel sluggish.
- Ignoring reduced-motion preferences.
- Infinite motion that distracts from the content.
- Relying on animation to convey essential information.
Where to go next
CSS motion covers most of what interfaces need. When you want timelines, scroll-driven storytelling or precise control, move on to the Advanced Animation guide. Understand the cost with Browsers & Rendering, keep it fast with Web Performance, and start from CSS fundamentals.