🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

CSS Animations Guide 2026: Transitions, Keyframes and Scroll-Driven

⏱️3 min read  ·  588 words

CSS animations have become essential for modern web design. In 2026, with the View Transition API, scroll-driven animations, and @starting-style for entry animations now supported in all major browsers, CSS animations are more powerful than ever. This guide covers everything from basic transitions to advanced keyframe animations and new 2026 APIs.

Transitions — Simple State Changes

/* transition: property duration timing-function delay */
.button {
  background: #0066CC;
  transform: translateY(0);
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  transition: background 200ms ease,
              transform 150ms ease,
              box-shadow 200ms ease;
}

.button:hover {
  background: #0052A3;
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(0,102,204,0.3);
}

.button:active {
  transform: translateY(0);
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

/* Timing functions */
/* ease (default), linear, ease-in, ease-out, ease-in-out */
/* cubic-bezier(x1, y1, x2, y2) — custom curve */
.smooth { transition: transform 300ms cubic-bezier(0.16, 1, 0.3, 1); } /* ease-out-expo */
.bounce { transition: transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1); } /* bounce */

Keyframe Animations

/* Define the animation */
@keyframes fadeInUp {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* Apply animations */
.hero-title {
  animation: fadeInUp 600ms ease-out both;  /* both = apply forwards and backwards fill */
}

.loading-icon {
  animation: spin 1s linear infinite;
}

.highlight {
  animation: pulse 2s ease-in-out infinite;
}

/* Stagger children animations */
.card-list .card {
  animation: fadeInUp 400ms ease-out both;
}
.card-list .card:nth-child(1) { animation-delay: 0ms; }
.card-list .card:nth-child(2) { animation-delay: 100ms; }
.card-list .card:nth-child(3) { animation-delay: 200ms; }
.card-list .card:nth-child(4) { animation-delay: 300ms; }

@starting-style — Entry Animations (2024+)

/* Animate elements entering the DOM */
dialog, [popover] {
  opacity: 1;
  transform: scale(1);
  transition: opacity 200ms ease, transform 200ms ease,
              display 200ms ease allow-discrete; /* allow display changes */

  /* @starting-style defines state BEFORE element appears */
  @starting-style {
    opacity: 0;
    transform: scale(0.95);
  }
}

/* Exit animation via overlay::before-close (coming soon) */
/* Currently handle with JS class toggling */

Scroll-Driven Animations (2024+)

/* Animate based on scroll position — NO JavaScript needed! */

@keyframes reveal {
  from { opacity: 0; transform: translateY(30px); }
  to { opacity: 1; transform: translateY(0); }
}

.reveal-on-scroll {
  animation: reveal linear both;
  animation-timeline: view();        /* tied to element's position in viewport */
  animation-range: entry 0% entry 30%; /* animate when 0-30% of element enters */
}

/* Progress bar tied to page scroll */
.reading-progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  background: #0066CC;
  transform-origin: left;
  animation: scaleX linear;
  animation-timeline: scroll(root);   /* tied to document scroll */
}

@keyframes scaleX {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

View Transitions API

// Animate between page states or route changes
async function navigateTo(url) {
  // Start transition
  const transition = document.startViewTransition(async () => {
    // Update the DOM here
    const content = await fetchPage(url);
    document.body.innerHTML = content;
  });

  // Wait for transition complete
  await transition.finished;
}

// In Next.js / React — use experimental viewTransition
import { unstable_viewTransition as viewTransition } from 'react';

function handleNavigation() {
  viewTransition(() => {
    router.push('/new-page');
  });
}

/* CSS controls the animation */
::view-transition-old(root) {
  animation: slide-out 300ms ease-out;
}

::view-transition-new(root) {
  animation: slide-in 300ms ease-out;
}

@keyframes slide-out {
  to { transform: translateX(-100%); opacity: 0; }
}

@keyframes slide-in {
  from { transform: translateX(100%); opacity: 0; }
}

/* Per-element transitions */
.hero-image {
  view-transition-name: hero;  /* matched between pages */
}
/* Browser automatically morphs .hero-image between pages! */

Performance Tips

  • Animate only compositor-friendly properties: transform and opacity
  • Avoid animating: width, height, top, left, margin, padding — these cause layout
  • Use will-change sparingly: will-change: transform promotes to GPU layer
  • Respect reduced motion:

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Animation Library Comparison

Tool Use When
CSS transitions Simple hover effects, state changes
CSS keyframes Looping animations, loading spinners
Scroll-driven API Reveal on scroll, progress bars
View Transitions Page/route transitions
Framer Motion Complex React animations, gestures
GSAP Complex timeline animations, ScrollTrigger

CSS animations in 2026 can handle 80% of animation needs without JavaScript. Use CSS transitions for hover states, keyframes for loops and entrances, scroll-driven animations for reveal effects, and the View Transitions API for smooth page transitions. Reach for Framer Motion or GSAP only when CSS can’t handle the complexity.

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা