CSS View Transitions API Guide 2026 — Animations & Transitions
Technical Guide · Updated 2026

CSS View Transitions API:
The Complete Guide for Modern Web Developers

Everything you need to know about the View Transitions API in 2026 — from simple same-document animations to cross-document page transitions and the latest scoped view transitions. Practical code examples from a senior frontend developer.

Oleg Maximov May 22, 2026 20 min read

Introduction

For decades, smooth page transitions were the domain of JavaScript libraries and frameworks. React Router animations, Vue transition components, GSAP — all workarounds for a missing browser primitive. The CSS View Transitions API changes that fundamentally.

As of May 2026, the View Transitions API is production-ready across all major browsers. Same-document transitions (for SPAs) work in Chrome, Safari, and Firefox. Cross-document view transitions — animating between separate HTML pages during navigation — are now shippable in both Chromium and Safari, with Firefox following. The scoped view transitions announced at Google I/O 2026 bring scoped, element-level transitions for Canvas and SPA use cases.

In this guide, I cover every aspect of the View Transitions API: the core startViewTransition() method, cross-document navigation with the @view-transition CSS rule, custom animations with view-transition-name, the new scoped transitions, browser support matrices, and real-world patterns you can use today. I've been building with this API since Chrome 111 — these are battle-tested techniques.

If you're new to this API, think of it as the browser's native way to say: "capture the current page, change the DOM, then animate from the old state to the new one" — all declaratively, with zero JavaScript for basic cases.

What Is the View Transitions API?

The View Transitions API is a browser-native mechanism for animating changes between two DOM states. It works in three distinct modes:

  1. Same-document (SPA): JavaScript calls document.startViewTransition(callback) to animate a DOM update within a single page.
  2. Cross-document (MPA): Navigating between two separate HTML pages with the @view-transition CSS at-rule or <meta> tag triggers an automatic transition.
  3. Scoped (element-level): New in Chrome 148+, a transition constrained to a specific DOM subtree rather than the entire document. Announced at Google I/O 2026.

Under the hood, the browser captures a screenshot of the current state, lets you update the DOM, then captures the new state, and interpolates between the two using CSS animations. The browser handles all the pixel-level work — no requestAnimationFrame, no ResizeObserver, no manual interpolation logic.

Same-Document Transitions: startViewTransition()

Basic Usage

The simplest possible view transition — swapping content in a div:

async function switchView() {
  // Capture current state, update DOM, animate to new state
  const transition = await document.startViewTransition(async () => {
    // Your DOM mutation goes here
    contentDiv.innerHTML = newContent;
  });

  // Wait for the transition animation to complete
  await transition.finished;
}

That's it. The browser captures the before state (your current DOM), runs your callback (which modifies the DOM), captures the after state, and then runs a default crossfade animation between the two snapshots.

💡 Important: The callback passed to startViewTransition() should be an async function. The browser captures the current state before the callback runs and captures the new state after the promise resolves. If you use a synchronous callback, the new state is captured immediately — which works for simple mutations but may miss async rendering work.

Transition Lifecycle

Every view transition goes through a predictable lifecycle with several events you can hook into:

const transition = document.startViewTransition(async () => {
  updateDOM();
});

// Customize before animation starts
transition.ready.then(() => {
  // Set custom animation durations
  document.documentElement.style.setProperty('--vt-duration', '800ms');
});

// Handle reduced motion
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
  transition.skipTransition();
}

await transition.finished;

Cross-Document View Transitions (MPA)

This is the feature that makes the View Transitions API truly transformative. With cross-document view transitions, navigating between two separate HTML pages — an actual multi-page application (MPA) — produces a seamless animated transition. The browser captures the outgoing page, navigates to the new URL, renders the new page, and animates between the two.

Enabling Cross-Document Transitions

Add the @view-transition CSS at-rule to both the source and destination pages:

/* On both the outgoing and incoming page */
@view-transition {
  navigation: auto;
}

Alternatively, use a <meta> tag in the <head>:

<meta name="view-transition" content="same-origin">

Once enabled, all same-origin navigations between participating pages will automatically animate with a crossfade transition. Both pages must opt in — if only one has the rule, the transition is skipped.

⚠️ Important security note: Cross-document view transitions only work for same-origin navigations. Both pages must share the same protocol, domain, and port. Cross-origin navigations (e.g., linking to an external site) skip the transition entirely — the browser falls back to normal navigation.

Shared Element Transitions Between Pages

The real magic happens with shared elements. When an element exists on both the outgoing and incoming page with the same view-transition-name, the browser animates that element smoothly between its old and new positions, while the rest of the page crossfades. This is how you achieve the "material design" shared-axis transitions natively:

/* On page A — a product card image */
.product-image-main {
  view-transition-name: product-image;
}

/* On page B — the same image in a hero position */
.product-detail-image {
  view-transition-name: product-image;
}

When navigating from page A (product listing) to page B (product detail), the product image smoothly scales and repositions from its card position to its hero position. The rest of the page content crossfades. No JavaScript needed for the transition itself.

💡 Best practice: Each view-transition-name must be unique within a page. If two elements share the same name on the same page, the transition is skipped. Use unique names like product-card-1, product-card-2 in listing views and map them dynamically.

Customizing View Transition Animations

The default crossfade is clean but generic. You can customize every aspect of the animation using CSS pseudo-elements and @keyframes.

The Pseudo-Element Tree

During a view transition, the browser constructs a pseudo-element tree that represents the animation layers. The tree structure is:

How It Works

The browser renders the old and new snapshots into separate layers, places them in ::view-transition-old and ::view-transition-new, and runs default CSS animations:

Custom Animation Example

A slide-and-fade transition for the entire page:

/* Override the default animation for all groups */
::view-transition-old(root) {
  animation-duration: 400ms;
  animation-name: slide-out;
}

::view-transition-new(root) {
  animation-duration: 400ms;
  animation-name: slide-in;
}

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

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

Per-Element Custom Animations

Use named groups for element-specific animations. For example, a hero image that scales up while the rest of the page slides:

::view-transition-old(product-image) {
  animation-name: scale-down;
}

::view-transition-new(product-image) {
  animation-name: scale-up;
}

@keyframes scale-up {
  0%   { transform: scale(0.8); opacity: 0.5; }
  100% { transform: scale(1); opacity: 1; }
}

@keyframes scale-down {
  0%   { transform: scale(1); opacity: 1; }
  100% { transform: scale(1.2); opacity: 0; }
}

Accessibility: Respecting Reduced Motion

Always provide a prefers-reduced-motion fallback. Disable view transitions entirely or use shorter, subtler animations:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 0.01ms !important;
  }
}

Scoped View Transitions (Chrome 148+)

Announced at Google I/O 2026 in the "What's new in Web UI" session, scoped view transitions let you constrain a transition to a specific DOM element rather than the entire document. This is a critical improvement for:

Scoped Transition API

Pass a scope option to startViewTransition():

const contentArea = document.getElementById('main-content');

await document.startViewTransition({
  update: async () => {
    // Update only the main content area
    contentArea.innerHTML = newContent;
  },
  scope: contentArea
}).finished;

With scoped transitions, the old snapshot shows only the scope element's subtree, and the new snapshot shows the updated subtree. The rest of the page remains static — no unnecessary crossfade on the navigation bar or footer.

💡 Pro tip: Scoped view transitions pair perfectly with the HTML-in-Canvas API. When you update a DOM element that's rendered onto a Canvas, scope the transition to that element for smooth Canvas-side animations without full-page re-renders.

Cross-Document Scoped Transitions

New in Chrome 150+ (July 2026): Scoped transitions now work across documents for same-origin navigations. If both pages have a matching element with the same view-transition-name, only that element's transition is animated — the rest of the page crossfades normally. This is the ideal pattern for MPA sites with shared layouts (header, footer, sidebar) and a dynamic content area:

/* On both pages */
#content-area {
  view-transition-name: page-content;
}

/* Header and footer have no view-transition-name — they crossfade */

Browser Support in 2026

The View Transitions API has come a long way. Here's the exact status as of May 2026:

Feature Chrome Edge Safari Firefox
Same-document (startViewTransition) 111+ 111+ 18+ 121+
Cross-document (meta/@view-transition) 126+ 126+ 18+ 131+ (partial)
Scoped transitions (same-document) 148+ 148+
Cross-document scoped 150+ 150+
Custom animations (::view-transition-old/new) 111+ 111+ 18+ 121+
view-transition-name on multiple elements 111+ 111+ 18+ 121+

Key takeaway: Same-document transitions and cross-document transitions with basic crossfade are production-ready in all major browsers. Scoped transitions are Chrome-only in May 2026, but are expected to reach other browsers by late 2026 / early 2027 based on the WICG discussion momentum.

Real-World Patterns

1. SPA Navigation with Framework Integration

For React, Vue, or Svelte applications, wrap your router's navigation handler with startViewTransition(). The framework still renders the new component — the View Transitions API just animates the swap:

// React Router integration
import { useNavigate } from 'react-router-dom';

function useViewTransitionNavigate() {
  const navigate = useNavigate();

  return async (to, options) => {
    if (document.startViewTransition) {
      await document.startViewTransition(async () => {
        navigate(to, options);
      }).finished;
    } else {
      navigate(to, options);
    }
  };
}

2. Dynamic List with Insertions and Removals

When adding or removing items from a list, use view-transition-name on each item for smooth positional animations:

function addItem(items, newItem) {
  const list = document.getElementById('item-list');

  // Assign unique view-transition-name to each item
  list.querySelectorAll('.item').forEach((item, i) => {
    item.style.viewTransitionName = `item-${i}`;
  });

  document.startViewTransition(async () => {
    list.appendChild(createItemElement(newItem));
    // Reassign names after mutation
    list.querySelectorAll('.item').forEach((item, i) => {
      item.style.viewTransitionName = `item-${i}`;
    });
  });
}

3. Image Gallery with Shared Element

The classic use case — a thumbnail that expands to a full-size hero image with a morphing animation. Both the thumbnail and the hero share the same view-transition-name:

/* Thumbnail in gallery */
.gallery-thumb[data-id="photo-1"] {
  view-transition-name: gallery-photo-1;
}

/* Hero image on detail page */
.photo-hero[data-id="photo-1"] {
  view-transition-name: gallery-photo-1;
}

Navigating between the gallery and detail page produces a seamless morphing animation — the thumbnail scales up to fill the hero position while maintaining visual continuity.

4. Tab / Accordion Components

Use scoped view transitions for tab content panels. Only the panel content fades/slides, while the tab bar and the rest of the page remain static:

async function switchTab(tabIndex) {
  const panel = document.getElementById(`tab-panel-${tabIndex}`);

  await document.startViewTransition({
    update: async () => {
      panels.forEach(p => p.classList.remove('active'));
      panel.classList.add('active');
    },
    scope: panel.parentElement  // Only the panel container animates
  }).finished;
}

Performance Considerations

View Transitions are remarkably efficient because the browser handles all the compositing. However, there are important performance factors to consider:

Graceful Degradation and Progressive Enhancement

The View Transitions API is a progressive enhancement. Your page should work perfectly without it — transitions just make it better. Here's a safe feature detection pattern:

if ('startViewTransition' in document) {
  // Modern browser — use view transitions
  await document.startViewTransition(async () => {
    updateDOM();
  }).finished;
} else {
  // Fallback — just update the DOM
  updateDOM();
}

For cross-document transitions, the @view-transition CSS at-rule is ignored by browsers that don't support it, so it's safe to include unconditionally. The browser simply performs a normal navigation without animation.

FAQ

What is the CSS View Transitions API?
The CSS View Transitions API is a browser-native API that lets you animate DOM changes — adding, removing, or updating elements — with smooth transitions. It works by capturing the 'before' and 'after' states of the page as snapshots and interpolating between them using CSS animations. It supports both same-document transitions (SPA-like state changes) and cross-document transitions (navigating between separate HTML pages). For a deeper understanding of modern CSS alongside JavaScript, see my ES2026 Complete Guide.
How do I use startViewTransition in JavaScript?
Call document.startViewTransition(callback) where callback updates the DOM. The API captures the current page state, runs your callback that changes the DOM, captures the new state, then animates between the two. Example: await document.startViewTransition(() => { updateUI(); }).finished; The callback should be async for best results.
What are cross-document view transitions?
Cross-document view transitions let you animate between separate HTML pages during navigation. Add <meta name="view-transition" content="same-origin"> to both pages, and the browser automatically animates the transition when navigating between them. Both pages must be same-origin. Shared elements with matching view-transition-name animate smoothly between pages. For a complete comparison of SPA vs MPA architectures and how view transitions fit in, see my web application development guide.
What are scoped view transitions?
Scoped view transitions, announced at Google I/O 2026, let you constrain a view transition to a specific DOM subtree instead of the entire document. This is critical for Canvas-based applications (see my HTML-in-Canvas guide) and for SPAs where only part of the page changes. Use document.startViewTransition({ update: callback, scope: element }) to scope a transition.
How do I customize view transition animations?
Use CSS pseudo-elements ::view-transition-old (the outgoing snapshot) and ::view-transition-new (the incoming snapshot) combined with @keyframes animations. You can override animation-duration, animation-timing-function, and animation-name. Use view-transition-name for shared element transitions, which can crossfade independently from the rest of the page. For dynamic color transitions between themes, combine with CSS Relative Color Syntax to create smooth color animations driven by CSS custom properties.
Which browsers support the View Transitions API in 2026?
As of May 2026: Same-document (startViewTransition) is supported in Chrome 111+, Edge 111+, Safari 18+, and Firefox 121+. Cross-document view transitions are supported in Chrome 126+, Edge 126+, Safari 18+, and Firefox 131+ (partial). Scoped view transitions are available as a developer trial in Chrome 148+ and were highlighted at Google I/O 2026. For more on the latest Chrome capabilities, see my Google I/O 2026 Day 2 Recap.
Do view transitions work when JavaScript is disabled?
Yes, for cross-document view transitions. When you add the <meta> tag or use the CSS @view-transition rule, navigation transitions work without JavaScript. Same-document transitions (startViewTransition) require JavaScript because the DOM update itself needs scripting. For the best user experience, use cross-document transitions as a progressive enhancement and same-document transitions for SPA navigation.

Getting Started: Your First View Transition

Here's the simplest possible implementation — a page that smoothly animates between light and dark mode:

<button id="themeToggle">Toggle Theme</button>

<script>
  document.getElementById('themeToggle').addEventListener('click', async () => {
    await document.startViewTransition(async () => {
      document.body.classList.toggle('dark-mode');
    }).finished;
  });
</script>

That's it. One method call wraps a DOM mutation and the browser handles the animation. The theme toggle gets a smooth crossfade — no extra CSS, no animation libraries.

For cross-document transitions, add to your pages:

<meta name="view-transition" content="same-origin">

And all same-origin navigations between pages with this meta tag will animate with a smooth crossfade. Add view-transition-name to elements you want to morph between pages — product images, hero sections, logos — and the browser handles the rest. No JavaScript required for the animation itself.

The View Transitions API is one of the most impactful web platform additions in recent years. It makes polished, app-like transitions accessible to every website — from a simple blog to a complex SPA — with minimal code and maximum performance.

Need help implementing view transitions in your project? I'm available for frontend architecture consulting and development. View my services or contact me directly. For a broader look at how modern CSS features like container queries and view transitions work together, see my CSS Container Queries complete guide.

Contact

Building a modern web application with smooth transitions?

I build production web applications using the latest CSS and JavaScript APIs. Let's discuss your project — free consultation.