Skip to content

Web Development · CSS

CSS View Transitions and Scroll-Driven Animations: What's Now Baseline in 2026

Two browser APIs that used to require JavaScript libraries are now available in all major browsers. Here's what the View Transitions API and scroll-driven animations can do, with working code.

Anurag Verma

Anurag Verma

7 min read

CSS View Transitions and Scroll-Driven Animations: What's Now Baseline in 2026

Sponsored

Share

For years, adding page transitions meant choosing a library. Framer Motion for React. GSAP for anything JavaScript-heavy. Barba.js if you wanted to feel like a web veteran from 2018. All of them work well, but they also add kilobytes to your bundle and introduce a layer between you and what the browser already knows how to do.

Two browser APIs (the View Transitions API and scroll-driven animations) now have consistent support across Chrome, Firefox, and Safari. They don’t replace every animation use case, but for a large subset of what web developers reach for libraries to accomplish, they’re the simpler choice.

View Transitions: Animated Page Changes Without a Framework

The View Transitions API lets you animate between DOM states (including full page navigations) without JavaScript animation code.

The Basics

Wrap a DOM change in document.startViewTransition():

document.startViewTransition(() => {
  // Make your DOM change here
  document.getElementById('content').innerHTML = newContent
})

The browser captures a screenshot of the current state, makes the change, then animates between the two. By default it crossfades. That’s it for the most basic usage.

Customizing the Animation

The captured elements get pseudo-elements that you can style with CSS:

/* The exiting content */
::view-transition-old(root) {
  animation: 300ms ease-out both fade-out;
}

/* The entering content */
::view-transition-new(root) {
  animation: 300ms ease-in both fade-in;
}

@keyframes fade-out {
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
}

You can target individual elements by name instead of the whole page:

.product-card {
  view-transition-name: product-card;
}

::view-transition-old(product-card) {
  animation: 400ms ease-out slide-out-left;
}

::view-transition-new(product-card) {
  animation: 400ms ease-in slide-in-right;
}

This gives you element-level continuity across DOM changes. If a card exists before and after the transition, the browser morphs it smoothly from its old position to its new one.

Page Transitions in a Multi-Page App (MPA)

Until recently, the View Transitions API only worked for single-page apps where JavaScript controlled navigation. The @view-transition CSS rule changes this:

@view-transition {
  navigation: auto;
}

Add that rule to every page and the browser handles cross-document transitions automatically. No JavaScript required. Navigation between pages that share this declaration will cross-fade by default.

For custom per-route animations:

/* On the page you're leaving */
@keyframes slide-out {
  to { transform: translateX(-100%); }
}

/* On the page you're entering */
@keyframes slide-in {
  from { transform: translateX(100%); }
}

@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) {
    animation: slide-out 300ms ease-in;
  }
  ::view-transition-new(root) {
    animation: slide-in 300ms ease-out;
  }
}

The prefers-reduced-motion wrapper is not optional. Users who have asked for reduced motion in their OS settings need to be respected.

Browser Support in 2026

The View Transitions API for same-document transitions has been in Chrome since version 111 (March 2023). Firefox shipped it in version 130 (August 2024). Safari added it in Safari 18 (September 2024). Cross-document transitions (the @view-transition rule) followed shortly after in all three.

By May 2026, all three major browsers support both modes, covering over 95% of global traffic.

Scroll-Driven Animations

Scroll-driven animations let you tie CSS animations to scroll position rather than time. Previously this required JavaScript scroll event listeners or an IntersectionObserver. Now it’s a CSS-only feature.

The Two Scroll Timeline Types

Scroll timeline: tied to a scrollable container. The animation progresses as the user scrolls.

View timeline: tied to an element’s position in the viewport. The animation progresses as the element enters and exits the visible area.

Animating as the Page Scrolls

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

.reading-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: #0066ff;
  transform-origin: left;
  
  animation: progress-bar linear;
  animation-timeline: scroll(root);
  animation-fill-mode: both;
}

scroll(root) creates a scroll timeline tied to the document root. The .reading-progress bar scales from 0 to full width as the user scrolls the page from top to bottom. No JavaScript.

Animating Elements as They Enter the Viewport

@keyframes fade-up {
  from {
    opacity: 0;
    transform: translateY(32px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.reveal-on-scroll {
  animation: fade-up ease-out both;
  animation-timeline: view();
  animation-range: entry 0% entry 50%;
}

view() creates a view timeline for the element. animation-range: entry 0% entry 50% means the animation plays during the first half of the element entering the viewport. Once it’s 50% visible, the animation is complete.

This replaces a common pattern that used to require:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('visible')
      observer.unobserve(entry.target)
    }
  })
}, { threshold: 0.1 })

document.querySelectorAll('.reveal-on-scroll').forEach(el => observer.observe(el))

The CSS version is shorter and more declarative. The JavaScript version gives you more control when you need it.

Parallax Without JavaScript

@keyframes parallax-shift {
  from { transform: translateY(-20%); }
  to   { transform: translateY(20%); }
}

.parallax-image {
  animation: parallax-shift linear;
  animation-timeline: scroll(root);
  animation-range: 0% 100%;
}

This shifts the image vertically as the user scrolls. For backgrounds and decorative elements, it creates a parallax effect without a JavaScript library.

Browser Support

Scroll-driven animations shipped in Chrome 115 (July 2023). Firefox shipped them in version 133 (November 2024). Safari added support in Safari 18.2 (December 2024).

By 2026, support is consistent across all major browsers. One practical note: Safari has had some edge-case inconsistencies with animation-range timing. Test on Safari if you’re using complex range combinations.

Pairing the Two APIs

The real power comes when you combine both. An element transitions via the View Transitions API when the URL changes, while other elements animate in based on scroll position as the user reads the page.

// Intercept link clicks for view transitions
document.addEventListener('click', (e) => {
  const link = e.target.closest('a[href]')
  if (!link || link.href.startsWith('mailto')) return
  if (!document.startViewTransition) return // graceful degradation
  
  e.preventDefault()
  
  document.startViewTransition(async () => {
    const response = await fetch(link.href)
    const html = await response.text()
    const parser = new DOMParser()
    const doc = parser.parseFromString(html, 'text/html')
    
    document.querySelector('main').replaceWith(doc.querySelector('main'))
    history.pushState({}, '', link.href)
  })
})

The page content morphs when navigating. Then scroll-driven animations handle the individual elements appearing as the user scrolls the new page.

What These APIs Don’t Cover

Both APIs work well for transitions tied to navigation and scroll. They don’t handle:

  • Complex interaction-driven animations (hover states with spring physics, drag gestures, gesture-based transitions). Framer Motion and GSAP are still the right tools here.
  • Choreographed sequences where you need timeline control across many elements. GSAP’s gsap.timeline() has no CSS equivalent.
  • Canvas or WebGL animations. Nothing in this spec touches those.

For straightforward page transitions and scroll-reveal effects, the native APIs are now good enough. For complex, orchestrated motion, reach for a library. The two aren’t in competition. They solve different scopes of the same general problem.

The Practical Upshot

If you’re building a content site, marketing page, or any MPA in 2026, the @view-transition { navigation: auto } rule deserves to be in your baseline CSS. It takes 10 seconds to add and immediately makes navigation feel more polished.

Scroll-driven animations replace a common use of IntersectionObserver with declarative CSS. For reveal effects and reading progress bars, the CSS version is less code and easier to maintain.

Start with the defaults. Customize when the defaults aren’t enough. Add libraries when the spec’s limits become the bottleneck.

Sponsored

Enjoyed it? Pass it on.

Share this article.

Sponsored

The dispatch

Working notes from
the studio.

A short letter twice a month — what we shipped, what broke, and the AI tools earning their keep.

No spam, ever. Unsubscribe anytime.

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored