All posts
cssanimations

CSS Animations: A Practical Guide for Full-Stack Developers

A practical guide to CSS animations — keyframes, transitions, performance-safe properties, and respecting reduced motion.

SR

Suhail Roushan

August 6, 2026

·
4 min read
·
0 views

CSS animations run on the compositor thread when you animate the right properties, and janky animations almost always come down to animating the wrong ones.

CSS animations let you change property values over time using @keyframes and the animation shorthand, or simpler one-off transitions using the transition property — all without JavaScript. The performance difference between a buttery-smooth animation and a laggy one usually isn't about how complex the animation is; it's about whether you animated transform/opacity or something that forces the browser to recalculate layout on every frame.

Why CSS Animations Matter (and When to Skip Them)

CSS animations run independently of the JavaScript main thread for compositor-friendly properties, which means they stay smooth even while your app is busy doing other work — something a requestAnimationFrame loop in JS can't guarantee under load. They're the right tool for hover states, loading spinners, entrance/exit transitions, and micro-interactions.

Skip CSS for animations that need to respond to complex user input mid-animation, or that require precise physics-based motion — that's when a JS animation library (Framer Motion, GSAP) earns its keep.

Getting Started with CSS Animations

transition handles simple state changes:

.button {
  background: #111;
  transition: background 0.2s ease, transform 0.2s ease;
}
.button:hover {
  background: #333;
  transform: translateY(-2px);
}

@keyframes handles anything more complex than a two-state transition:

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

.skeleton {
  animation: pulse 1.5s ease-in-out infinite;
}

Core Animation Concepts Every Developer Should Know

Only transform and opacity are "compositor-safe." Animating width, height, top, left, or margin forces the browser to recalculate layout on every single frame — that's what causes visible jank on lower-end devices. Animate transform: translateX() instead of left, and transform: scale() instead of width/height.

/* Janky — triggers layout every frame */
.bad { transition: left 0.3s; }

/* Smooth — runs on the compositor thread */
.good { transition: transform 0.3s; }

animation-timing-function shapes the motion curve. ease-out feels natural for elements entering the screen, ease-in for elements leaving — matching real-world physics (things decelerate as they arrive, accelerate as they leave).

will-change hints to the browser to optimize ahead of time, but overusing it on many elements actually hurts performance by consuming extra GPU memory. Apply it sparingly, and only right before the animation starts (often via JS, removed after it ends).

Prefers-reduced-motion is not optional accessibility polish. Some users get genuinely dizzy or nauseated from motion — respecting this media query is a baseline requirement, not a nice-to-have:

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Common CSS Animation Mistakes and How to Fix Them

Mistake 1: animating box-shadow directly for a "glow" effect. Box-shadow changes trigger paint on every frame, which is expensive. Fix: animate opacity on a pseudo-element that already has the shadow applied, or use filter: drop-shadow() which is compositor-friendlier in modern browsers.

Mistake 2: forgetting prefers-reduced-motion. Shipping an animation-heavy site without this media query excludes users who've explicitly told their OS they don't want motion. Fix: add the media query above as a baseline in your global CSS, every project.

Mistake 3: infinite animations that never pause off-screen. An animation: spin 2s infinite on an element scrolled out of view still consumes CPU/GPU cycles. Fix: use the IntersectionObserver API to pause animations on elements outside the viewport for anything animation-heavy below the fold.

When Should You Use CSS Animations Instead of a JS Library?

Use CSS for anything state-driven and relatively simple — hover effects, loading states, toggles, entrance transitions triggered by a class change. Reach for a JS library when you need interruptible animations (like a drag gesture that can reverse mid-motion), spring physics, or animations sequenced based on runtime data.

CSS Animations in Production

Test animations on an actual low-end device or with CPU throttling in DevTools, not just your dev machine — a 60fps animation on an M-series laptop can easily drop to 15fps on a budget Android phone if you're animating the wrong properties. And always pair animation/transition with the reduced-motion media query — it's a five-line addition that meaningfully improves accessibility.

Before shipping any new animation, check DevTools' Performance panel for one thing: is it painting on every frame, or compositing? If it's painting, switch to transform/opacity.

Related posts

Written by Suhail Roushan — Full-stack developer. More posts on AI, Next.js, and building products at suhailroushan.com/blog.

Get in touch