The Two CSS Animation Systems
CSS has two ways to animate: transitions (for state changes triggered externally) and animations (for self-playing sequences). Both have their place:
/* Transition: responds to class/state change */
.btn {
background: #7c5cfc;
transition: background 0.2s ease, transform 0.2s ease;
}
.btn:hover {
background: #9333ea;
transform: translateY(-2px);
}
/* Animation: runs automatically */
.spinner {
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes Syntax
The @keyframes rule defines the sequence of styles at specific points in time. You can use percentages or the keywords from / to:
@keyframes fadeSlideIn {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Multi-step keyframes */
@keyframes pulse {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.05); opacity: 0.8; }
100% { transform: scale(1); opacity: 1; }
}
The animation Shorthand
The animation property is shorthand for 8 individual properties:
.element {
animation: name duration timing-function delay
iteration-count direction fill-mode play-state;
/* Example */
animation: fadeSlideIn 0.4s ease-out 0.1s 1 normal forwards running;
/* Most common — name, duration, easing */
animation: fadeSlideIn 0.4s ease-out;
/* Multiple animations */
animation: fadeSlideIn 0.4s ease-out, pulse 2s ease-in-out 1s infinite;
}
Timing Functions Explained
The timing function controls the rate of change throughout the animation. Understanding the options is key to animations that feel natural:
.element {
/* Built-in keywords */
animation-timing-function: ease; /* slow start, fast middle, slow end */
animation-timing-function: ease-in; /* slow start, accelerates */
animation-timing-function: ease-out; /* fast start, decelerates */
animation-timing-function: ease-in-out; /* slow start and end */
animation-timing-function: linear; /* constant speed (good for rotations) */
/* Custom cubic-bezier */
animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); /* spring bounce */
/* Steps (for sprite animations or typewriter effects) */
animation-timing-function: steps(8, end);
}
For most UI animations, ease-out feels the most natural — objects that enter the screen decelerate as they arrive, like real physical objects. Reserve ease-in for elements leaving the screen.
fill-mode: What Happens Before and After
animation-fill-mode controls the element's style outside the animation's active duration:
.element {
/* none (default): returns to original state after animation */
animation-fill-mode: none;
/* forwards: keeps final keyframe values after animation ends */
animation-fill-mode: forwards;
/* backwards: applies first keyframe values during delay period */
animation-fill-mode: backwards;
/* both: applies backwards + forwards */
animation-fill-mode: both;
}
/* Most fade-in animations need "both" to avoid flash before start */
.fade-in {
opacity: 0;
animation: fadeIn 0.5s ease-out 0.2s both;
}
@keyframes fadeIn { to { opacity: 1; } }
Performance: Only Animate These Properties
CSS animations can trigger three different browser pipeline stages: layout, paint, and composite. Only composited properties are GPU-accelerated and smooth at 60fps:
/* ✅ GPU-composited: always smooth */
transform: translate(), scale(), rotate(), skew()
opacity
/* ⚠️ Triggers paint: avoid animating on many elements */
color, background-color, border-color, box-shadow
/* ❌ Triggers layout: never animate these */
width, height, padding, margin, top, left, font-size
/* Promote to GPU layer if needed */
.animated-element {
will-change: transform; /* Use sparingly — right before animation */
}
Accessibility: Respecting Motion Preferences
Users with vestibular disorders or motion sensitivity can set their OS to "reduce motion". Always respect this preference:
/* Define animation normally */
.card {
animation: slideIn 0.5s ease-out both;
}
/* Disable or reduce for users who prefer less motion */
@media (prefers-reduced-motion: reduce) {
.card {
animation: none; /* or a gentler alternative */
}
/* For transitions */
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
The nuclear option (setting all durations to 0.01ms) works but may cause jarring instant state changes. A better approach is to define a simplified, opacity-only version of each animation for reduced-motion users rather than removing all animation entirely.
Common Animation Patterns
These are the animations that appear most frequently in production UI:
/* Fade in */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* Slide up (enter from below) */
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Scale pop (for modals, tooltips) */
@keyframes scalePop {
from { opacity: 0; transform: scale(0.92); }
to { opacity: 1; transform: scale(1); }
}
/* Shimmer (loading skeleton) */
@keyframes shimmer {
from { background-position: -200% center; }
to { background-position: 200% center; }
}
.skeleton {
background: linear-gradient(90deg, #1a1a2e 25%, #2a2a4e 50%, #1a1a2e 75%);
background-size: 200% auto;
animation: shimmer 1.5s linear infinite;
}
Common Mistakes
- Animating layout properties: Animating
width,height,top, ormarginforces layout recalculation on every frame. Always usetransformandopacityinstead. - Forgetting prefers-reduced-motion: Approximately 1 in 5 users has some form of motion sensitivity. Ignoring this preference causes real harm and can create legal accessibility liability.
- Using will-change on everything:
will-change: transformcreates a new compositing layer, consuming GPU memory. Don't apply it globally — add it right before animation starts and remove it after. - Too many simultaneous animations: Animating 20 cards at once is much more expensive than animating 1. Stagger entrance animations to reduce concurrent GPU load.
- Infinite animations on large elements: An infinitely spinning large element repaints on every frame. Use the technique of animating a pseudo-element's opacity instead of the element itself.