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.
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:
// 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:
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:
::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:
/* 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 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>
<!-- Enable transitions between pages -->
<meta name="view-transition" content="same-origin">
</head>
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:
// 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:
/* 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;
}
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:
// 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
/* 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
// 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
/* 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.
Click the toggle to change theme with transition.
Demo: theme switching with View Transitions
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.
// 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.
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 = '';
});
});
});
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-nameproperty 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!
Discover our premium templates that use the View Transitions API for exceptional user experiences.