Introduction
The View Transitions API represents a fundamental shift in how we think about navigation within web applications. For years, developers have struggled to create smooth, native-app-like transitions between pages, resorting to complex JavaScript libraries, Single Page Application routers, or accepting the jarring experience of full page reloads. The View Transitions API, now supported in all major browsers, provides a standardized way to create sophisticated transitions with minimal code.
This API allows browsers to capture snapshots of the current state and the new state, then animate between them using CSS. What makes this revolutionary is its simplicity: you write a few lines of CSS and the browser handles the complex work of capturing DOM states, cross-fading, and animating elements that persist across views.
As we move through 2026, the View Transitions API has matured significantly, with expanded capabilities for customizing animations, coordinating complex sequences, and handling edge cases. This guide explores everything you need to create polished, professional transitions that rival native mobile applications.
Understanding View Transitions
The View Transitions API enables animated transitions between different document states, whether those states come from navigation or DOM changes within a single page.
How It Works
When a view transition is triggered, the browser performs a sophisticated sequence:
- Capture Phase: The browser takes a snapshot of the current DOM state, capturing the visual appearance of all elements
- Transition Phase: The old and new states exist simultaneously as the animation plays
- Completion Phase: The new state becomes fully interactive and the old snapshot is removed
This architecture means you don’t need to worry about the complexity of synchronizing animations—the browser manages the entire lifecycle.
Basic Usage
The simplest view transition requires just one line of CSS and one property:
/* Enable view transitions globally */
@view-transition {
navigation: auto;
}
With this single CSS rule, navigations between pages automatically animate with a cross-fade effect. The old page fades out while the new page fades in, creating a smooth, native-app-like experience.
For Single Page Applications where JavaScript handles navigation, trigger transitions programmatically:
// Start a named view transition
function navigateTo(url) {
if (!document.startViewTransition) {
// Fallback for unsupported browsers
window.location.href = url;
return;
}
document.startViewTransition(() => {
window.location.href = url;
});
}
The callback function performs the actual navigation or DOM change. The browser automatically captures the before and after states and animates between them.
The View Transition Runner
For more control, the startViewTransition method returns a transition object:
function handleNavigation() {
const transition = document.startViewTransition(() => {
updateContent();
});
transition.ready.then(() => {
console.log('Animation started');
});
transition.finished.then(() => {
console.log('Animation completed');
});
}
These promises enable coordinating other animations, analytics, or cleanup tasks with the transition lifecycle.
Customizing Animations
The default cross-fade works, but the API provides extensive customization options for creating distinctive experiences.
Named View Transitions
Assign names to specific elements to create coordinated animations:
<!-- Old state <h1 class="hero -->
<header>
-title">Welcome</h1>
</header>
<!-- New state -->
<header>
<h1 class="hero-title">About Us</h1>
</header>
@view-transition {
navigation: auto;
}
.hero-title {
view-transition-name: page-title;
}
Elements with the same view-transition-name automatically animate between their positions. The title shrinks or grows, moves to its new location, and morphs between styles—all automatically.
/* Customize the title transition */
::view-transition-old(page-title),
::view-transition-new(page-title) {
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
Animation Keyframes
Define custom animations for any view transition pseudo-element:
::view-transition-old(root) {
animation: fade-out 0.3s ease-out;
}
::view-transition-new(root) {
animation: fade-in 0.3s ease-in;
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; transform: translateX(-30px); }
}
@keyframes fade-in {
from { opacity: 0; transform: translateX(30px); }
to { opacity: 1; transform: translateX(0); }
}
This creates a slide-and-fade effect that feels more dynamic than a simple cross-fade.
Different Animations for Different Elements
Create varied animations for different page sections:
/* Header slides from top */
header {
view-transition-name: page-header;
}
::view-transition-old(page-header) {
animation: slide-from-top 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
::view-transition-new(page-header) {
animation: slide-to-position 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes slide-from-top {
from { transform: translateY(-100%); }
to { transform: translateY(0); }
}
@keyframes slide-to-position {
from { transform: translateY(0); }
to { transform: translateY(0); }
}
/* Content fades in */
main {
view-transition-name: page-content;
}
::view-transition-old(page-content) {
animation: fade-out 0.3s ease-out;
}
::view-transition-new(page-content) {
animation: fade-in 0.3s ease-out;
}
Grouping Elements
Use view-transition-group to animate multiple related elements together:
/* Animate all product card elements as a unit */
.product-card {
view-transition-name: product;
}
.product-image {
view-transition-name: product-image;
}
.product-title {
view-transition-name: product-title;
}
The browser intelligently handles z-index and layering, ensuring elements appear in the correct visual order during transitions.
Advanced Patterns
Beyond basic page navigation, the View Transitions API supports sophisticated patterns for complex applications.
Hero Animations
The classic “hero” animation—where an image expands from a thumbnail to full size—becomes straightforward:
<!-- Product listing -->
<a href="/product/123">
<img src="thumb.jpg" class="product-thumb">
</a>
<!-- Product detail page -->
<img src="full.jpg" class="product-hero">
.product-thumb,
.product-hero {
view-transition-name: product-image;
}
.product-thumb {
width: 200px;
height: 200px;
}
.product-hero {
width: 100%;
max-width: 600px;
}
When navigating from the listing to detail page, the image automatically animates from its thumbnail size and position to its hero size. The browser handles the complex math of coordinate transformation.
Persistent Elements
Elements that appear on both pages can persist without transition:
.navigation,
.sidebar {
view-transition-name: none;
}
Setting view-transition-name: none excludes an element from the transition entirely. The old version disappears and the new version appears, creating continuity for persistent UI elements like navigation.
Multiple Named Transitions
Create complex sequences with multiple named transitions:
.hero-title { view-transition-name: title; }
.hero-image { view-transition-name: hero; }
.product-price { view-transition-name: price; }
.product-description { view-transition-name: description; }
::view-transition-old(title),
::view-transition-new(title) {
animation-duration: 0.5s;
}
::view-transition-old(hero),
::view-transition-new(hero) {
animation-duration: 0.6s;
animation-delay: 0.1s;
}
::view-transition-old(price),
::view-transition-new(price) {
animation-duration: 0.3s;
animation-delay: 0.3s;
}
::view-transition-old(description),
::view-transition-new(description) {
animation-duration: 0.4s;
animation-delay: 0.4s;
}
Staggered animations create a polished, choreographed feel that guides user attention through the changes.
Coordinating with JavaScript
JavaScript can intercept and customize transitions:
document.addEventListener('viewtransitionprepare', (e) => {
// Called when transition is being prepared
const transition = e.viewTransition;
// Customize specific element transitions
const titleTransition = transition.namedElements.get('page-title');
if (titleTransition) {
titleTransition.options = {
duration: 500,
easing: 'cubic-bezier(0.4, 0, 0.2, 1)'
};
}
});
document.addEventListener('viewtransitionstart', (e) => {
// Called when animation begins
analytics.track('page_transition', {
from: e.from,
to: e.viewTransition
});
});
document.addEventListener('viewtransitionfinish', (e) => {
// Called when transition completes
console.log('Transition finished');
});
These events enable integrating transitions with analytics, logging, or coordinating with other animations.
Single Page Application Integration
View Transitions work exceptionally well within Single Page Applications, providing the smooth navigation users expect.
Router Integration
Integrate with common SPA routers:
// Example with a simple router
class Router {
constructor() {
this.routes = {};
}
add(path, handler) {
this.routes[path] = handler;
}
navigate(path) {
const handler = this.routes[path];
if (!handler) return;
document.startViewTransition(() => {
handler();
history.pushState(null, '', path);
});
}
}
The router wraps its content update in startViewTransition, enabling animations for every navigation.
Framework Integration
React, Vue, and other frameworks can integrate view transitions:
// React component
import { useNavigate } from 'react-router-dom';
function ProductCard({ product }) {
const navigate = useNavigate();
const handleClick = () => {
document.startViewTransition(() => {
navigate(`/products/${product.id}`);
});
};
return (
<div onClick={handleClick}>
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
</div>
);
}
The pattern is similar across frameworks—wrap navigation logic in startViewTransition and let the browser handle the animation.
Preserving Scroll Position
Control scroll behavior during transitions:
@view-transition {
navigation: auto;
}
/* Disable scroll restoration animation */
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none;
mix-blend-mode: normal;
}
For more control, handle scroll restoration in JavaScript:
function navigate(url) {
document.startViewTransition(async () => {
// Save scroll position
const scrollY = window.scrollY;
// Navigate
await fetch(url);
renderContent();
// Restore scroll after transition
window.scrollTo(0, 0);
});
}
Back Navigation
View Transitions handle back navigation automatically:
// When user clicks back, browser triggers transition
window.addEventListener('popstate', () => {
document.startViewTransition(() => {
history.back();
renderPreviousContent();
});
});
The transition runs in reverse, creating a natural back-and-forth feel.
Performance Considerations
View Transitions are optimized by the browser, but understanding performance helps create smooth experiences.
What Gets Captured
The browser captures visual snapshots of elements with a view-transition-name. Elements without this property are not captured and won’t animate.
/* Only transition what matters */
.animated-element {
view-transition-name: animated;
}
.static-element {
/* No transition */
}
Limiting transitioned elements improves performance.
Large Element Considerations
Very large elements or many transitioning elements can impact performance:
/* Optimize large images */
.hero-image {
view-transition-name: hero;
/* Force GPU acceleration */
will-change: transform;
}
/* Reduce transition complexity during animation */
::view-transition-active(*),
::view-transition-current(*),
::view-transition-old(*),
::view-transition-new(*) {
will-change: transform, opacity;
}
Test on lower-end devices to ensure animations remain smooth.
Reducing Motion
Respect user preferences:
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Users who prefer reduced motion get instant transitions without animation.
Real-World Examples
Practical examples demonstrate how to apply view transitions in production applications.
Gallery Transitions
Photo galleries benefit particularly from view transitions:
/* Thumbnail grid */
.gallery-grid img {
view-transition-name: gallery-image;
}
/* Lightbox/detail view */
.gallery-detail img {
view-transition-name: gallery-image;
}
/* Smooth expansion animation */
::view-transition-old(gallery-image) {
animation: thumbnail-to-hero 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
::view-transition-new(gallery-image) {
animation: thumbnail-to-hero 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes thumbnail-to-hero {
from {
object-fit: cover;
border-radius: 8px;
}
to {
object-fit: contain;
border-radius: 0;
}
}
The image smoothly expands from its grid position to fill the lightbox.
Form Wizards
Multi-step forms can transition smoothly:
.wizard-step {
view-transition-name: wizard-content;
}
.wizard-step-1 { view-transition-name: step-1; }
.wizard-step-2 { view-transition-name: step-2; }
.wizard-step-3 { view-transition-name: step-3; }
::view-transition-old(step-1),
::view-transition-new(step-2) {
animation: slide-left 0.4s ease-in-out;
}
::view-transition-old(step-2),
::view-transition-new(step-1) {
animation: slide-right 0.4s ease-in-out;
}
Steps slide in from the appropriate direction based on navigation flow.
Dashboard Updates
Real-time dashboards can animate data changes:
function updateDashboard(data) {
document.startViewTransition(() => {
updateChartData(data.chart);
updateMetrics(data.metrics);
updateList(data.recent);
});
}
Data updates animate smoothly rather than jarring users with instant changes.
Modal Transitions
Opening and closing modals:
.modal-overlay {
view-transition-name: modal;
}
.modal-content {
view-transition-name: modal-content;
}
::view-transition-old(modal),
::view-transition-new(modal) {
animation: fade 0.2s ease-out;
}
::view-transition-old(modal-content) {
animation: scale-down 0.2s ease-out;
}
::view-transition-new(modal-content) {
animation: scale-up 0.2s ease-out;
}
@keyframes scale-down {
from { transform: scale(1); opacity: 1; }
to { transform: scale(0.95); opacity: 0; }
}
@keyframes scale-up {
from { transform: scale(0.95); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
The modal content scales while the overlay fades, creating a polished open/close experience.
Browser Support and Fallbacks
The View Transitions API has excellent support in 2026, but graceful degradation remains important.
Browser Support
As of 2026, the View Transitions API is supported in:
- Chrome/Edge 111+
- Firefox 123+
- Safari 17+
- Opera 97+
For unsupported browsers, provide a fallback:
function navigateWithTransition(url) {
if (!document.startViewTransition) {
window.location.href = url;
return;
}
document.startViewTransition(() => {
window.location.href = url;
});
}
CSS Fallback
Define fallback animations in CSS:
@supports (view-transition-name: none) {
/* View Transitions available */
@view-transition {
navigation: auto;
}
}
@supports not (view-transition-name: none) {
/* Fallback animations */
.page-content {
animation: fade-in 0.3s ease-out;
}
}
Progressive Enhancement
Always ensure content is accessible regardless of transitions:
<!-- Content visible even without JS -->
<main>
<h1>Welcome</h1>
<p>Content here</p>
</main>
<!-- Transitions enhance but aren't required -->
<script>
// Add transition logic
</script>
Best Practices
Following established patterns produces the best results.
Subtle Transitions
Small animations feel more polished than dramatic ones:
/* Subtle fade */
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.2s;
animation-timing-function: ease-out;
}
Overly long or complex transitions frustrate users who want to navigate quickly.
Consistent Timing Functions
Use consistent easing across transitions:
:root {
--transition-ease: cubic-bezier(0.4, 0, 0.2, 1);
}
::view-transition-old(*),
::view-transition-new(*) {
animation-timing-function: var(--transition-ease);
}
Consistent easing makes the interface feel cohesive.
Test Navigation Patterns
Test all navigation scenarios:
- Forward navigation
- Back navigation
- Direct URL access
- Refresh during transition
- Network failure mid-transition
Unexpected behavior in edge cases damages user trust.
Document Transitions
Use unique names for clarity:
/* Clear naming */
.product-image { view-transition-name: product-hero-image; }
.page-title { view-transition-name: main-heading; }
.sidebar { view-transition-name: navigation-panel; }
/* Avoid generic names */
.element-1 { view-transition-name: el1; } /* Confusing */
Conclusion
The View Transitions API transforms web navigation from a series of jarring cuts into a continuous, animated experience. By moving this capability into the browser, the API makes sophisticated transitions accessible to every developer, regardless of framework or JavaScript expertise.
The key to success lies in restraint—subtle, purposeful transitions enhance usability while dramatic effects quickly become tiresome. Start with the default cross-fade, then carefully enhance specific elements with meaningful animations.
As browser support solidifies and developer familiarity grows, view transitions will become a standard expectation rather than a delightful surprise. Building this capability into your applications today prepares you for the web that users expect in 2026 and beyond.
Comments