Home / Articles / Animation & Motion /

animation

@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;
}
Use 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 transform instead of top / left.
  • Keep will-change to 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.