Introduction
CSS transitions and animations handle the majority of UI animations — hover effects, state changes, loading indicators — without JavaScript. They run on the compositor thread when animating transform and opacity, achieving 60fps even on mobile.
Understanding which properties to animate and how timing functions work separates amateur animations from professional ones.
This guide covers transitions, keyframe animations, and modern techniques like view transitions.
Key Concepts
CSS Transitions
.button {
background: #3b82f6;
transition: background-color 0.2s ease, transform 0.15s ease-out;
}
.button:hover {
background: #2563eb;
transform: translateY(-2px);
}
Timing Functions
/* ease-out: entrances (fast arrive, slow settle) */
/* ease-in: exits (slow start, fast leave) */
/* Custom cubic-bezier */
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); /* Expo out */
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); /* Back out */
Keyframe Animations
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.card { animation: fadeInUp 0.5s ease-out forwards; }
.card:nth-child(2) { animation-delay: 0.1s; }
.card:nth-child(3) { animation-delay: 0.2s; }
Practical Examples
1. Hover Card Effect
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}
2. Loading Spinner
@keyframes spin { to { transform: rotate(360deg); } }
.spinner {
width: 24px; height: 24px;
border: 3px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
3. Accordion with Grid
.accordion-content {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 0.3s ease;
}
.accordion-content.open {
grid-template-rows: 1fr;
}
.accordion-content > div { overflow: hidden; }
4. View Transitions API
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: fade-out 0.2s ease; }
::view-transition-new(root) { animation: fade-in 0.3s ease; }
.hero-image { view-transition-name: hero; }
Best Practices
- ✅ Only animate transform and opacity for 60fps performance
- ✅ Use ease-out for entrances, ease-in for exits
- ✅ Keep transitions 150-300ms for micro-interactions
- ✅ Respect prefers-reduced-motion
- ❌ Don't animate width, height, top, left — use transform
- ❌ Don't use transition: all — specify exact properties
Common Pitfalls
- 🚫 Animating layout properties — causes reflow jank
- 🚫 transition: all — animates unintended properties
- 🚫 Animations too long — keep UI transitions under 400ms
- 🚫 Ignoring prefers-reduced-motion