Articles /
@keyframes: CSS animation sequences
Move beyond simple transitions. Keyframe animations give you multi-step sequences, looping, and fine-grained timing control — all in pure CSS.
@keyframes syntax
Define named keyframe blocks with percentage stops or the from / to shorthand. The browser interpolates between each stop.
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(1rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.hero-text {
animation: slide-up 0.6s ease-out both;
}
Multi-step animations
Use percentage stops for complex choreography. You can list multiple properties at each stop.
@keyframes pulse {
0% { box-shadow: 0 0 0 0 oklch(65% 0.2 270 / 0.5); }
50% { box-shadow: 0 0 0 12px oklch(65% 0.2 270 / 0); }
100% { box-shadow: 0 0 0 0 oklch(65% 0.2 270 / 0); }
}
.notification-dot {
animation: pulse 2s ease-in-out infinite;
}
Fill mode and iteration
animation-fill-mode decides what styles apply before and after the animation runs.
forwards— retains the final keyframe styles after completion.backwards— applies the first keyframe styles during the delay.both— combines forwards and backwards.
.card {
animation: slide-up 0.5s ease-out both;
animation-iteration-count: 1; /* default */
animation-delay: 0.15s;
}
both as your default fill mode. It prevents the flash of unstyled content before a delayed animation starts.Direction and play-state
animation-direction controls whether the animation reverses on alternate cycles. animation-play-state lets you pause and resume.
.spinner {
animation: rotate 1s linear infinite;
}
.spinner.is-paused {
animation-play-state: paused;
}
@keyframes rotate {
to { transform: rotate(360deg); }
}
Staggering with custom properties
Combine animation-delay with a per-element custom property to create stagger effects without JavaScript.
.list-item {
animation: slide-up 0.4s ease-out both;
animation-delay: calc(var(--i, 0) * 0.08s);
}
/* set --i in the HTML via inline style */
/* style="--i: 0" ... style="--i: 4" */
Performance best practices
Animations run on the compositor thread only when they animate transform, opacity, or filter. Anything else drops to the main thread and risks jank.
- Animate
transforminstead oftop/left. - Keep
will-changeto the animated property, and remove it after the animation ends. - Always gate looping animations behind
prefers-reduced-motion. - Test on low-end devices — 60 fps on a laptop does not guarantee 60 fps on a phone.