View Transitions Api

Production engineering guide for view transitions api covering patterns, implementation strategies, and operational best practices.

View Transitions API enables declarative, performant, and coordinated animations between views in a web application. It is essential when navigating between pages, switching between states, or animating between different content layouts, ensuring that transitions feel seamless and responsive. The API is most valuable in SPAs, progressive web apps, and any client-side application where visual continuity between states is critical.

Defining View Transitions

A view transition is a coordinated animation between two views: the *old view* (current) and the *new view* (target). The browser tracks visual elements across the transition and composites them using a single, high-performance rendering pass. To define a view transition, you declare view-transition-name CSS properties on elements and invoke startViewTransition() in JavaScript.

Creating a View Transition

To initiate a view transition, call startViewTransition() with a callback that updates the DOM. The callback runs in a new frame, and the browser captures the visual state of the old view and applies it to a *transition container*.

document.getElementById('nav-link').addEventListener('click', async (event) => {
  event.preventDefault();
  const href = event.currentTarget.href;

  const transition = document.startViewTransition(async () => {
    // Update the DOM in the new view
    const response = await fetch(href, { headers: { 'Accept': 'text/html' } });
    const html = await response.text();
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    const newContent = doc.querySelector('#main-content');
    document.getElementById('main-content').replaceWith(newContent);

    // Update the URL
    history.pushState(null, '', href);
  });

  // Optional: add a background to the transition
  transition.ready.then(() => {
    document.body.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
  });
});

The startViewTransition() method returns a ViewTransition object. The ready promise resolves when the transition is fully rendered and ready for user interaction.

Naming Views with view-transition-name

To define which elements transition between views, assign view-transition-name CSS values to elements in both the old and new view. The browser matches elements with the same name across the transition.

.hero-image {
  view-transition-name: hero;
}

.hero-text {
  view-transition-name: hero-text;
}

.nav-link {
  view-transition-name: nav-link;
}

When navigating, the browser identifies hero elements in both views and animates them as a single unit. The animation is computed using the *bounding box*, *position*, *size*, and *content* of the elements. The browser synthesizes a composite layer from the old view and overlays the new view, applying the transition animation.

Transitioning Between Views

The transition begins immediately after startViewTransition() is called. The browser captures the current DOM, composites the old view, and applies the new view on top. The animation is triggered by the view-transition CSS property, which controls how the transition is animated.

.view-transition {
  view-transition-name: page;
  view-transition-duration: 0.6s;
  view-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* Optional: define the transition behavior */
.view-transition::view-transition {
  background: #f0f0f0;
  backdrop-filter: blur(10px);
  border-radius: 12px;
}

The ::view-transition pseudo-element represents the transition container. It is rendered once per transition and can be styled independently. This layer is used to composite the old and new views during the transition.

Coordinating Animations

View transitions work best when combined with CSS animations and keyframes. The browser automatically animates all elements with the same view-transition-name, but you can augment this with @keyframes and animation rules.

.hero-image {
  view-transition-name: hero;
  width: 100%;
  height: 400px;
  object-fit: cover;
}

.hero-image::view-transition {
  background: #000;
  opacity: 0.8;
}

@keyframes slideIn {
  from {
    transform: translateX(100px);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

.hero-text {
  view-transition-name: hero-text;
  animation: slideIn 0.6s ease-out forwards;
}

The browser synchronizes the animation timing across the transition, ensuring that elements animate together even if the new view is loaded asynchronously.

Advanced Patterns

Handling Asynchronous View Updates

When the new view is fetched asynchronously, the transition may appear to "pop" before the content arrives. To avoid this, use transition.ready to animate the background or overlay after the DOM update.

const transition = document.startViewTransition(async () => {
  const response = await fetch('/blog/post-1');
  const html = await response.text();
  const doc = new DOMParser().parseFromString(html, 'text/html');
  const newPost = doc.querySelector('#post');
  document.getElementById('content').replaceWith(newPost);
});

transition.ready.then(() => {
  document.body.classList.add('transition-complete');
});

This ensures that the background or overlay animation starts only after the DOM has settled, giving the illusion of instant navigation.

Handling Multiple Transitions

You can have multiple independent transitions in a single navigation. The browser composites all active transitions, allowing layered animations.

// Transition 1: hero image
document.querySelector('.hero-link').addEventListener('click', async (e) => {
  e.preventDefault();
  const transition1 = document.startViewTransition(async () => {
    const response = await fetch(e.currentTarget.href);
    const html = await response.text();
    const doc = new DOMParser().parseFromString(html, 'text/html');
    const content = doc.querySelector('#hero-content');
    document.getElementById('hero').replaceWith(content);
  });

  transition1.ready.then(() => {
    document.body.style.backgroundImage = 'url(/hero-bg-2.jpg)';
  });
});

// Transition 2: navigation bar
document.querySelector('.nav-link').addEventListener('click', async (e) => {
  e.preventDefault();
  const transition2 = document.startViewTransition(async () => {
    const response = await fetch(e.currentTarget.href);
    const html = await response.text();
    const doc = new DOMParser().parseFromString(html, 'text/html');
    const nav = doc.querySelector('nav');
    document.querySelector('header').replaceWith(nav);
  });

  transition2.ready.then(() => {
    document.querySelector('header').style.transform = 'translateY(-10px)';
  });
});

Each startViewTransition() call creates a separate transition container, and the browser composites them in the correct order.

Fallbacks and Edge Cases

View Transitions API is not universally supported. The browser may fall back to a simple transition or animate() API when startViewTransition() is unavailable. To ensure consistency, use feature detection.

if ('startViewTransition' in document) {
  // Use View Transitions API
  const transition = document.startViewTransition(async () => {
    // Update DOM
  });
} else {
  // Fallback: use CSS transitions or keyframe animations
  const element = document.getElementById('content');
  element.style.transition = 'all 0.4s ease-out';
  element.style.transform = 'translateX(-100px)';
  element.style.opacity = 0;

  const newContent = await fetch('/page').then(r => r.text());
  element.innerHTML = newContent;

  requestAnimationFrame(() => {
    element.style.transform = 'translateX(0)';
    element.style.opacity = 1;
  });
}

Silent Failures and Common Pitfalls

Performance and Best Practices

Failure Modes

API Reference

The View Transitions API is a powerful tool for creating rich, responsive navigation experiences. It is not a drop-in replacement for CSS animations, but a coordinated, compositing-level mechanism that works best when combined with semantic naming and careful layout design.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.