CSS Animations
Create complex, looping, and multi-step animations with @keyframes — control timing, iteration, direction, and fill modes for polished motion design.
@keyframes Fundamentals
Keyframes define animation steps at percentage points or from/to keywords. The animation property applies a named keyframe rule to an element, controlling duration, timing, delay, iteration count, and direction.
Unlike transitions, keyframe animations can run automatically without a trigger state change. They are ideal for loading spinners, attention pulses, and ambient background effects.
- Use from/to for simple two-step animations
- Percentage keyframes allow precise multi-step control
- Name keyframes descriptively: slideInLeft, pulseGlow, shimmer
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reveal {
animation: fadeInUp 600ms ease both;
}Animation Properties
Animation-duration sets how long one cycle takes. Animation-delay waits before starting. Animation-iteration-count controls repeats — use infinite for loaders. Animation-direction supports normal, reverse, alternate, and alternate-reverse.
The animation shorthand combines all properties: animation: name duration timing delay iteration direction fill-mode.
.spinner {
animation: spin 800ms linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}Animation Fill Mode
Fill mode controls what happens before and after the animation runs. backwards applies the first keyframe during delay. forwards retains the last keyframe after completion. both combines both behaviors.
Without fill-mode: forwards, elements snap back to their original state when the animation ends. This is a common source of bugs in entrance animations.
.toast-enter {
animation: slideIn 400ms ease forwards;
}
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}Complex Multi-step Animations
Multiple keyframe stops create sophisticated effects. A skeleton loading shimmer uses 0%, 50%, and 100% stops. Staggered list animations delay each item with animation-delay based on index via CSS custom properties or nth-child.
Keep animation count low on screen simultaneously — more than a handful of concurrent animations can cause jank on mobile devices.
- Use animation-delay with nth-child for staggered reveals
- Pause animations on hover with animation-play-state: paused
- Combine @keyframes with CSS variables for dynamic animation values
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}Performance Considerations
Animate only transform and opacity for best performance — these properties composite on the GPU without triggering layout or paint. Avoid animating width, height, top, or margin which force expensive reflows.
Use will-change sparingly to hint the browser about upcoming animations, but remove it after the animation completes to free GPU memory.
.animated-card {
will-change: transform, opacity;
}
.animated-card.done {
will-change: auto;
}