View Transitions API: Native Page Animations Without JavaScript Libraries (2026)

Developer May 30, 2026 · OTPZap Team

In 2025 Chrome shipped View Transitions API for multi-page navigation. In 2026 Firefox and Safari finally support too. This is a big underrated change: page transition animations that previously needed heavy frameworks (Framer Motion, GSAP, Lenis) can now be pure browser native.

Result: smaller bundle size, better performance, and UX that finally feels like native apps. But many developers don't know this API exists or how to use it.

This article is a practical guide to View Transitions API from basic to advanced patterns.

What is View Transitions API

Browser API allowing seamless transitions between different UI states with animation. 2 modes:

Same-Document Transitions

Within SPA, when you update DOM (route change, modal open, list item add/remove), browser can animate transitions between old and new states.

Cross-Document Transitions (MPA)

Between different page navigations (multi-page apps). Click link, browser animates transition between pages without jarring page reload.

Both modes use same underlying mechanism: browser snapshots old DOM, renders new DOM, animates between them via CSS.

Basic Same-Document Transition

Simplest implementation, for SPA:

// JavaScript: trigger transition
function navigate(newUrl) {
  if (!document.startViewTransition) {
    // Fallback for unsupported browsers
    updateContent(newUrl);
    return;
  }
  
  document.startViewTransition(() => {
    updateContent(newUrl);
  });
}

function updateContent(url) {
  // Update DOM as usual
  document.querySelector('main').innerHTML = newContent;
}

By default browser applies cross-fade animation between old and new states. Without any CSS, you already get smooth fade transition.

Customize with CSS

Default cross-fade boring. Customize via CSS:

/* Old state exit animation */
::view-transition-old(root) {
  animation: 300ms cubic-bezier(0.4, 0, 0.2, 1) both fade-out;
}

/* New state enter animation */
::view-transition-new(root) {
  animation: 300ms cubic-bezier(0.4, 0, 0.2, 1) both fade-in;
}

@keyframes fade-out {
  to { opacity: 0; transform: translateX(-30px); }
}

@keyframes fade-in {
  from { opacity: 0; transform: translateX(30px); }
}

Slide-in from left, slide-out to left. Similar to native iOS navigation.

Advanced: Element-Specific Transitions

For transitions between same element across 2 pages (like hero image staying, navbar moving position), use view-transition-name:

/* CSS: give unique name per element to track */
.hero-image {
  view-transition-name: hero-photo;
}

.user-avatar {
  view-transition-name: avatar;
}

Browser tracks elements with same name in old and new states, animates transitions between them. Like shared element transitions on Android.

Popular use case: photo grid → photo detail. Photo expands smoothly from thumbnail to full size.

Cross-Document Transitions (MPA)

For multi-page apps (like server-rendered, traditional MPA), add to CSS:

@view-transition {
  navigation: auto;
}

Done. Browser auto-enables transitions when user navigates between same-origin pages. No JavaScript needed.

Plus customize per route:

/* Specific transition for certain page */
[data-page="home"]::view-transition-new(root) {
  animation: 400ms ease-out forwards slide-up;
}

@keyframes slide-up {
  from { transform: translateY(20px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

Real Use Cases That Are Very Useful

1. Modal Open/Close

Without Framer Motion, smooth modal animation:

function openModal() {
  if (!document.startViewTransition) return showModal();
  
  document.startViewTransition(() => {
    document.querySelector('.modal').style.display = 'block';
  });
}

/* CSS */
.modal {
  view-transition-name: main-modal;
}

::view-transition-old(main-modal),
::view-transition-new(main-modal) {
  animation-duration: 200ms;
}

2. List Item Animation

Add/remove items from list with smooth animation:

function addItem(item) {
  document.startViewTransition(() => {
    const li = document.createElement('li');
    li.style.viewTransitionName = `item-${item.id}`;
    li.textContent = item.name;
    list.appendChild(li);
  });
}

/* CSS for delete animation */
::view-transition-old(item-*) {
  animation: 200ms ease-out forwards slide-out-right;
}

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

3. Tab Switching

Smooth content swap when user clicks tabs. Previously needed framework or library, now native.

4. Theme Switching

Light/dark mode toggle with smooth color transition. Combine with CSS variables.

5. Full Page Transitions in MPA

Server-rendered apps (Rails, Django, PHP, Laravel) historically clunky page transitions, can now be smooth without migrating to SPA.

Browser Support Status 2026

Coverage sufficient for production. For older browsers, fallback to standard navigation (no animation, but still works).

Performance Considerations

1. Transitions Cost CPU/GPU

Browser snapshots DOM and animates. For very complex pages (1000+ DOM nodes), transitions can be janky on low-end devices.

Solution: limit transition scope. Use view-transition-name only for important elements, so browser doesn't snapshot whole page.

2. Animation Duration

Default 250ms. For page transitions, 300-400ms is usually sweet spot. Longer = feels sluggish. Shorter = not perceived.

3. Reduced Motion

Honor user preference:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}

Common Pitfalls

1. Async Updates in startViewTransition

Callback in startViewTransition must be synchronous DOM update or return Promise:

// Wrong - async without await
document.startViewTransition(() => {
  fetch('/api/data').then(r => updateDOM(r));
});

// Right - return promise
document.startViewTransition(async () => {
  const data = await fetch('/api/data').then(r => r.json());
  updateDOM(data);
});

2. View Transition Name Collision

Cannot have 2 elements with same view-transition-name at the same time. If collision, transitions don't work for those elements.

3. Z-index and Stacking Context

Transitions create temporary stacking context. Elements with high z-index might behave weird during animation. Test edge cases.

4. Form State Loss

When transitions replace DOM, form state can be lost. For forms users are filling, save state to localStorage or handle preservation manually.

Comparison with Alternatives

vs Framer Motion

vs GSAP

vs Custom CSS Transitions

Migration Strategy

If you already use animation libraries, no need to rip-and-replace. Strategy:

  1. Identify simple animations where library is overkill (page transitions, basic fades)
  2. Replace one-by-one with VT API
  3. Keep library for complex animations VT API doesn't handle
  4. Bundle size shrinks, performance improves gradually

Closing

View Transitions API is one of the most impactful but underrated browser additions in 2024-2026. Not a revolution from capability standpoint, but massive simplification of developer effort for UX already common in native apps.

For web developers in 2026: spend 2-3 hours learning this API. Pattern is straightforward, browser support mature, and can be applied directly to existing projects without framework migration. Very high ROI.

What you don't need: continuing to use Framer Motion or GSAP for every animation. Modern web dev workflow in 2026 mostly uses VT API for page transitions + a bit of library specific for complex cases.