Blog / JavaScript / CSS

View Transitions API: The Complete Guide

Master the View Transitions API to create smooth page transitions and shared element animations. A native API that revolutionizes the user experience on the web.

Introduction to the View Transitions API

The View Transitions API is a new native API that enables smooth transitions between different DOM states or between entire pages. No more complex JavaScript libraries to animate your page changes!

Before this API, creating page transitions required solutions like:

  • Barba.js or Swup for SPA transitions
  • Complex FLIP animations with JavaScript
  • CSS transitions limited to animatable properties

With the View Transitions API, the browser natively handles screenshot capture, interpolation and animation between two states. The result: optimal performance and much simpler code.

💡
Good to know

The View Transitions API works for both Single Page Applications (SPA) and Multi Page Applications (MPA). MPA transitions require Chrome 126+ with a specific meta tag.

Basic syntax: document.startViewTransition()

The main method of the API is document.startViewTransition(). It takes a callback function that performs the DOM modifications.

Simple transition

Here is the most basic implementation to animate a content change:

basic-transition.js
// Function that modifies the DOM
function updateContent() {
    document.querySelector('.content').innerHTML = newContent;
}

// Check API support
if (!document.startViewTransition) {
    // Fallback: direct update without animation
    updateContent();
    return;
}

// Create an animated transition
document.startViewTransition(() => {
    updateContent();
});

Transition with Promise

The method returns a ViewTransition object with several useful properties:

transition-promises.js
const transition = document.startViewTransition(async () => {
    // Load the new content
    const response = await fetch('/api/content');
    const html = await response.text();
    document.querySelector('.content').innerHTML = html;
});

// Wait for the transition to be ready
await transition.ready;
console.log('Transition started');

// Wait for animation to finish
await transition.finished;
console.log('Transition finished');

The ViewTransition object exposes three promises:

  • updateCallbackDone: resolved when the callback is complete
  • ready: resolved when the animation can begin
  • finished: resolved when the animation is complete

Custom CSS transitions

By default, the View Transitions API applies a simple crossfade. But you can fully customize the animations with CSS pseudo-elements.

Transition pseudo-elements

During a transition, the browser creates a pseudo-element structure:

pseudo-elements.txt
::view-transition
  ::view-transition-group(root)
    ::view-transition-image-pair(root)
      ::view-transition-old(root)   /* Capture of the old state */
      ::view-transition-new(root)   /* Capture of the new state */

Custom fade animation

Here is how to create a fade animation with a zoom effect:

custom-fade.css
/* Old state animation (exit) */
::view-transition-old(root) {
    animation: 300ms ease-out fade-out-scale;
}

/* New state animation (entry) */
::view-transition-new(root) {
    animation: 300ms ease-out fade-in-scale;
}

@keyframes fade-out-scale {
    to {
        opacity: 0;
        transform: scale(0.9);
    }
}

@keyframes fade-in-scale {
    from {
        opacity: 0;
        transform: scale(1.1);
    }
}

Slide animation

For a slide-type transition between pages:

slide-transition.css
/* Slide to the left (forward) */
::view-transition-old(root) {
    animation: 400ms cubic-bezier(0.4, 0, 0.2, 1) slide-out-left;
}

::view-transition-new(root) {
    animation: 400ms cubic-bezier(0.4, 0, 0.2, 1) slide-in-right;
}

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

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

/* Reverse slide (backward) - add a class on html */
html.back-navigation::view-transition-old(root) {
    animation-name: slide-out-right;
}

html.back-navigation::view-transition-new(root) {
    animation-name: slide-in-left;
}

Demo: click the tabs to see the transition

Page transitions (MPA)

The most anticipated feature: transitions between pages in classic multi-page applications. No more need for a SPA to have smooth transitions!

Enabling MPA transitions

Simply add this meta tag in the <head> of each page:

head-meta.html
<head>
    <!-- Enable transitions between pages -->
    <meta name="view-transition" content="same-origin">
</head>
⚠️
Important

MPA transitions only work between pages of the same origin (same-origin). Navigation to external domains will not trigger a transition.

Customizing transition direction

Use JavaScript to define the direction based on navigation:

navigation-direction.js
// Detect navigation direction
window.addEventListener('pageswap', (event) => {
    const navigation = performance.getEntriesByType('navigation')[0];

    if (navigation.type === 'back_forward') {
        document.documentElement.classList.add('back-navigation');
    }
});

// Clean up after transition
window.addEventListener('pagereveal', () => {
    document.documentElement.classList.remove('back-navigation');
});

Shared element animations

The true power of the API lies in shared element transitions. An element can "fly" from one position to another between two states.

Naming transition elements

Use the CSS property view-transition-name to identify elements that should have their own animation:

shared-elements.css
/* List page: product card */
.product-card img {
    view-transition-name: product-image;
}

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

/* Detail page: same names */
.product-hero img {
    view-transition-name: product-image;
}

.product-hero h1 {
    view-transition-name: product-title;
}
💡
Important rule

Each view-transition-name must be unique within the document. If two elements share the same name at the same time, the transition will fail silently.

Dynamic names with JavaScript

For lists of elements, assign names dynamically:

dynamic-names.js
// On card click
card.addEventListener('click', (e) => {
    const productId = card.dataset.id;

    // Assign a unique name to this card
    card.querySelector('img').style.viewTransitionName = `product-${productId}`;

    // Navigate to detail page
    window.location.href = `/product/${productId}`;
});

Customizing a shared element animation

shared-animation.css
/* The group handles position and size */
::view-transition-group(product-image) {
    animation-duration: 500ms;
    animation-timing-function: cubic-bezier(0.4, 0, 0, 1);
}

/* The old state fades out progressively */
::view-transition-old(product-image) {
    animation: 250ms ease-out fade-out;
}

/* The new state appears progressively */
::view-transition-new(product-image) {
    animation: 250ms ease-in 250ms fade-in;
}
🎨
🎵
📷
🎓
🚀
💎

Demo: click an element to simulate a shared transition

Browser support and fallbacks

The View Transitions API is still relatively new. Here is the current support status:

Browser SPA (same-document) MPA (cross-document) Minimum version
Chrome Yes Yes 111+ / 126+
Edge Yes Yes 111+ / 126+
Opera Yes Yes 97+ / 112+
Safari Yes No 18+
Firefox Flag No -

Feature detection

feature-detection.js
// Check JavaScript support
const supportsViewTransitions = 'startViewTransition' in document;

// Check CSS support
const supportsCSSViewTransitions = CSS.supports('view-transition-name', 'test');

// Wrapper function with fallback
function navigate(updateFn) {
    if (!supportsViewTransitions) {
        updateFn();
        return;
    }

    document.startViewTransition(updateFn);
}

CSS with fallback

css-fallback.css
/* Transition styles only if supported */
@supports (view-transition-name: test) {
    .hero-image {
        view-transition-name: hero;
    }

    ::view-transition-old(hero),
    ::view-transition-new(hero) {
        animation-duration: 400ms;
    }
}

Practical examples

Example 1: Theme switch with transition

A classic: switching between light/dark theme with an elegant animation.

Current mode

Click the toggle to change theme with transition.

Demo: theme switching with View Transitions

theme-switch.js
const toggle = document.querySelector('#theme-toggle');

toggle.addEventListener('click', async () => {
    // Fallback without animation
    if (!document.startViewTransition) {
        document.documentElement.classList.toggle('dark');
        return;
    }

    // With animated transition
    const transition = document.startViewTransition(() => {
        document.documentElement.classList.toggle('dark');
    });

    // Animate from the toggle button
    await transition.ready;

    const { top, left, width, height } = toggle.getBoundingClientRect();
    const x = left + width / 2;
    const y = top + height / 2;
    const endRadius = Math.hypot(
        Math.max(x, innerWidth - x),
        Math.max(y, innerHeight - y)
    );

    document.documentElement.animate(
        {
            clipPath: [
                `circle(0px at ${x}px ${y}px)`,
                `circle(${endRadius}px at ${x}px ${y}px)`
            ]
        },
        {
            duration: 500,
            easing: 'ease-out',
            pseudoElement: '::view-transition-new(root)'
        }
    );
});

Example 2: SPA navigation

Implement smooth transitions in your Single Page Application.

spa-navigation.js
// Simple router with View Transitions
class Router {
    constructor() {
        this.routes = new Map();
        this.container = document.querySelector('#app');

        // Intercept link clicks
        document.addEventListener('click', (e) => {
            const link = e.target.closest('a[href^="/"]');
            if (link) {
                e.preventDefault();
                this.navigate(link.pathname);
            }
        });
    }

    register(path, handler) {
        this.routes.set(path, handler);
    }

    async navigate(path) {
        const handler = this.routes.get(path);
        if (!handler) return;

        const updateDOM = async () => {
            this.container.innerHTML = await handler();
            history.pushState(null, '', path);
        };

        // With View Transition if supported
        if (document.startViewTransition) {
            await document.startViewTransition(updateDOM).finished;
        } else {
            await updateDOM();
        }
    }
}

// Utilisation
const router = new Router();
router.register('/', () => '<h1>Home</h1>');
router.register('/about', () => '<h1>About</h1>');

Example 3: Image gallery

Smooth transition between thumbnail and full screen.

image-gallery.js
const gallery = document.querySelector('.gallery');
const modal = document.querySelector('.modal');
const modalImg = modal.querySelector('img');

gallery.querySelectorAll('img').forEach((img, index) => {
    img.addEventListener('click', () => {
        // Assign a unique name
        img.style.viewTransitionName = 'gallery-image';
        modalImg.style.viewTransitionName = 'gallery-image';

        const transition = document.startViewTransition(() => {
            modalImg.src = img.src;
            modal.classList.add('open');
        });

        transition.finished.then(() => {
            // Clean up names after transition
            img.style.viewTransitionName = '';
        });
    });
});
🚀
Performance

View Transitions use GPU compositing. The animation is performed on "screenshots" of the states, guaranteeing 60fps even with complex DOMs.

Conclusion

The View Transitions API represents a major step forward for user experience on the web. It finally makes it possible to create native, performant, and easy-to-implement page transitions.

Key takeaways:

  • Simple to use: a single method document.startViewTransition()
  • Fully customizable with CSS and dedicated pseudo-elements
  • Shared elements: the view-transition-name property enables "hero" animations
  • MPA support: also works between real HTML pages
  • Progressive enhancement: easy to implement with fallback

Browser support is improving rapidly. Now is the ideal time to start experimenting with this API and preparing your projects for the future of the web!

🎯
Go further

Discover our premium templates that use the View Transitions API for exceptional user experiences.