View Transitions API - Smooth State Changes

What You'll Learn

  • Understand what the View Transitions API provides and why it's revolutionary
  • Use document.startViewTransition() to create automatic crossfade animations
  • Apply view-transition-name to elements for named, customizable transitions
  • Customize transitions with ::view-transition-old() and ::view-transition-new() pseudo-elements
  • Create smooth layout transitions (list ↔ grid, sidebar toggles, tab switches)
  • Detect browser support and provide graceful fallbacks
  • Build practical examples: image galleries, expandable cards, layout switches
  • Follow best practices for performance and accessibility

Introduction to View Transitions

The View Transitions API is a game-changer for web animations. Instead of manually orchestrating complex CSS animations and JavaScript timing, you simply wrap your DOM updates in document.startViewTransition(), and the browser automatically creates smooth crossfade transitions between the old and new states.

Before View Transitions, creating smooth state changes required:

  • Capturing screenshots of the old state
  • Positioning elements precisely
  • Coordinating multiple animations
  • Managing z-index and timing
  • Cleaning up after animations complete

Now, the browser handles all of this automatically. You just update the DOM, and the browser smoothly transitions between states - animating position, size, opacity, and other properties seamlessly.

Basic View Transitions

The simplest use case is wrapping a DOM update in document.startViewTransition(). The browser automatically creates a crossfade animation.

// Without View Transitions (instant change) function toggleBox() { box.classList.toggle('large'); } // With View Transitions (smooth animation) function toggleBox() { document.startViewTransition(() => { box.classList.toggle('large'); }); } .box { width: 100px; height: 100px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px; } .box.large { width: 200px; height: 200px; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border-radius: 50%; /* square → circle */ } /* No additional CSS needed - automatic transition! */

Named View Transitions

While default transitions provide a crossfade, named transitions let you customize animations for specific elements. Assign a view-transition-name to elements, then target them with CSS pseudo-elements.

/* Assign a unique transition name */ .card { view-transition-name: custom-card; } /* Define custom slide animations */ @keyframes slide-from-right { from { transform: translateX(100%); } } @keyframes slide-to-left { to { transform: translateX(-100%); } } /* Customize the old state (exiting) */ ::view-transition-old(custom-card) { animation: 300ms cubic-bezier(0.4, 0, 1, 1) both fade-out, 300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left; } /* Customize the new state (entering) */ ::view-transition-new(custom-card) { animation: 300ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in, 300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; } function toggleCard() { document.startViewTransition(() => { card.classList.toggle('expanded'); }); }

View Transition Pseudo-elements

The browser creates several pseudo-elements during transitions that you can style with CSS:

/* Root of the entire transition overlay */ ::view-transition { /* Usually don't need to style this */ } /* Group container for a named transition */ ::view-transition-group(name) { /* Controls how old and new are composited */ } /* Container for old/new snapshots */ ::view-transition-image-pair(name) { /* Isolates the two images */ } /* Snapshot of the old state (exiting) */ ::view-transition-old(name) { animation: 300ms ease-out fade-out; } /* Live rendering of the new state (entering) */ ::view-transition-new(name) { animation: 300ms ease-in fade-in; } /* Target the default transition (no name) */ ::view-transition-old(root) { animation: 200ms ease-out zoom-out; }

Layout Transitions

View Transitions excel at layout changes. By maintaining the same view-transition-name across layouts, elements smoothly morph between positions and sizes.

<!-- List layout --> <div class="list-container"> <div class="list-item" style="view-transition-name: item-a;">Product A</div> <div class="list-item" style="view-transition-name: item-b;">Product B</div> <div class="list-item" style="view-transition-name: item-c;">Product C</div> </div> <!-- Grid layout (same transition names!) --> <div class="grid-container"> <div class="grid-item" style="view-transition-name: item-a;">Product A</div> <div class="grid-item" style="view-transition-name: item-b;">Product B</div> <div class="grid-item" style="view-transition-name: item-c;">Product C</div> </div> function toggleLayout() { document.startViewTransition(() => { if (isGridLayout) { container.className = 'list-container'; // Update HTML to list structure } else { container.className = 'grid-container'; // Update HTML to grid structure } isGridLayout = !isGridLayout; }); }

JavaScript API

The document.startViewTransition() method returns a ViewTransition object with promises for controlling timing:

// Basic usage document.startViewTransition(() => { updateDOM(); }); // With promises for timing control const transition = document.startViewTransition(() => { updateDOM(); }); // Wait for transition to be ready (snapshots taken) await transition.ready; console.log('Transition animation starting'); // Wait for transition to finish await transition.finished; console.log('Transition complete'); // Skip the transition (instant change) transition.skipTransition(); // Handle errors transition.finished.catch(err => { console.log('Transition interrupted'); });

Browser Support and Feature Detection

View Transitions are a modern feature with growing but limited browser support. Always check support and provide fallbacks.

// Feature detection if ('startViewTransition' in document) { // View Transitions supported document.startViewTransition(() => { updateDOM(); }); } else { // Fallback: just update without transition updateDOM(); } // Reusable helper function withViewTransition(updateCallback) { if (!document.startViewTransition) { updateCallback(); return; } document.startViewTransition(updateCallback); } // Usage withViewTransition(() => { element.classList.toggle('active'); });

Common Use Cases

View Transitions are perfect for:

Single Page Application Navigation

// Smooth page transitions in SPAs router.beforeTransition((to, from) => { document.startViewTransition(() => { renderPage(to); }); });

Image Gallery Expansion

<div class="gallery"> <img style="view-transition-name: image-1;" onclick="expand(this)"> <img style="view-transition-name: image-2;" onclick="expand(this)"> </div> <script> function expand(img) { document.startViewTransition(() => { img.classList.toggle('fullscreen'); }); } </script>

Modal Dialogs

function openModal(modalId) { const modal = document.getElementById(modalId); document.startViewTransition(() => { modal.classList.add('open'); }); }

Theme Switching

function toggleTheme() { document.startViewTransition(() => { document.documentElement.classList.toggle('dark-mode'); }); }

Sorting and Filtering

function sortItems(order) { document.startViewTransition(() => { const sorted = items.sort(order); renderItems(sorted); }); }

Best Practices

Follow these guidelines for optimal View Transitions:

1. Always Provide Fallbacks

// Good: Graceful degradation if (document.startViewTransition) { document.startViewTransition(() => updateDOM()); } else { updateDOM(); // Works without transitions }

2. Keep Transitions Short

/* Good: Quick, responsive (200-400ms) */ ::view-transition-old(root) { animation: 300ms ease-out fade-out; } /* Avoid: Too slow, feels sluggish */ ::view-transition-old(root) { animation: 1000ms ease-out fade-out; /* Too long! */ }

3. Use Unique Transition Names

/* Good: Unique names */ .card-1 { view-transition-name: card-1; } .card-2 { view-transition-name: card-2; } /* Bad: Duplicate names (only one will transition) */ .card { view-transition-name: card; } /* Multiple elements! */

4. Respect User Preferences

/* Disable transitions for users who prefer reduced motion */ @media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; } }

5. Limit Simultaneous Transitions

// Good: Transition one section at a time document.startViewTransition(() => { updateSection(currentSection); }); // Avoid: Too many elements transitioning document.startViewTransition(() => { updateEntireDOM(); // Hundreds of elements! Slow! });

6. Test on Lower-End Devices

Key Takeaways

  • View Transitions API provides automatic crossfade animations between DOM states
  • document.startViewTransition() wraps DOM updates to enable smooth transitions
  • No manual animation needed - browser handles position, size, opacity changes automatically
  • view-transition-name assigns unique names for customizable transitions
  • ::view-transition-old() and ::view-transition-new() let you customize animations per element
  • Perfect for layout changes, image galleries, modals, SPAs, theme switching, and sorting
  • Always detect browser support with 'startViewTransition' in document
  • Provide fallbacks for unsupported browsers (just update DOM without transition)
  • Keep transitions short (200-400ms) for responsive feel
  • Transition names must be unique within the document
  • Respect prefers-reduced-motion for accessibility
  • Test performance on lower-end devices
  • Browser support: Chrome/Edge/Opera 111+, Safari/Firefox in development
  • The browser creates snapshots and handles all animation orchestration automatically