Skip to main content

08 - View Transitions API: Native Navigation Animations

The View Transitions API brings smooth, native-app animations directly to the browser, without third-party libraries. The same-document variant is Baseline 2025, while cross-document transitions reach full interoperability in 2026.

What do you think about this article?

08 - View Transitions API: Native Navigation Animations
Core Web VitalsPhotos:Bibek ghoshPexels

At a Glance

The View Transitions API brings smooth, native-app animations directly to the browser, without third-party libraries. The same-document variant is Baseline 2025, while cross-document transitions reach full interoperability in 2026.

View Transitions API: Native Navigation Animations

The View Transitions API brings smooth, native-app animations directly to the browser, without third-party libraries. Once activated, transitions between views in a SPA or between pages in a traditional site produce automatic, fluid visual transitions. The same-document variant is Baseline 2025 (universal support), while cross-document transitions reach full interoperability in 2026. With this API, your site stands out from the ordinary.

Until now, navigation animations required external libraries like Framer Motion or GSAP, or manual JavaScript with delicate timing and synchronization. The View Transitions API eliminates this complexity: the browser automatically manages the snapshot of the previous state, creates a pseudo-element overlay, and animates the transition with CSS rules you define. No libraries, no laborious JavaScript, no flicker risk.

What You'll Learn

  • Difference between same-document and cross-document view transitions
  • How to use document.startViewTransition() in SPAs
  • CSS ::view-transition and ::view-transition-old/new pseudo-elements
  • Apply view-transition-name for granular animation control
  • Integrate view transitions in Angular, React, Vue and modern frameworks
  • Fallback and progressive enhancement for legacy browsers
  • Accessibility with prefers-reduced-motion
  • Performance tips and common pitfalls

Same-Document vs Cross-Document View Transitions

The View Transitions API supports two different scenarios, each with different architectural implications:

  • Same-document transitions: View changes within the same page (SPA). Example: navigation between tabs, route changes in Angular/React. Baseline 2025, universal support.
  • Cross-document transitions: Navigation from one completely different HTML page to another (traditional MPA). Example: link from /article/1 to /article/2. Reaches Baseline in 2026, but Chrome and Edge have supported it since v111 (2023). Requires @view-transition HTML attribute and server/browser coordination.

Same-Document: Basics with startViewTransition()

To animate a same-document transition, the flow is straightforward:

  1. Wrap your DOM update inside document.startViewTransition(() => { ... })
  2. The browser captures a snapshot of the current state (old view)
  3. The callback updates the DOM (new view)
  4. The browser creates an overlay pseudo-element ::view-transition
  5. Animates the transition with CSS rules
// Example: route change in a SPA (Angular)
// In your navigation service or component

async navigateToArticle(id: number) {
  // Check if the API is available
  if ('startViewTransition' in document) {
    // Wrap the DOM update
    await (document as any).startViewTransition(async () => {
      // Update the DOM: reactive state, store, router, etc.
      this.currentArticleId = id;
      this.article$ = this.backend.getArticle(id);
      // The transition will animate the change automatically
    });
  } else {
    // Fallback: update without transition on legacy browsers
    this.currentArticleId = id;
    this.article$ = this.backend.getArticle(id);
  }
}

Pseudo-Elements ::view-transition

Once startViewTransition() is active, the browser automatically creates a pseudo-element structure:

/* The pseudo structure created by the browser (logical, not in real DOM) */
::view-transition
  ::view-transition-group(root)
    ::view-transition-image-pair(root)
      ::view-transition-old(root)  /* Screenshot of previous state */
      ::view-transition-new(root)  /* New view */

You can target these pseudo-elements with CSS to control the animation exactly as you wish:

/* Default fade animation (browser default) */
::view-transition {
  /* Customize the transition behavior */
}

/* Animate only the old view */
::view-transition-old(root) {
  animation: slide-out-left 0.6s cubic-bezier(0.4, 0, 1, 1);
}

/* Animate only the new view */
::view-transition-new(root) {
  animation: slide-in-right 0.6s cubic-bezier(0, 0, 0.2, 1);
}

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

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

view-transition-name: Granular Element Animations

By default, the entire page is animated as a single unit. If you want different elements to animate independently (e.g., header stays in place while content scrolls), use view-transition-name:

/* HTML */
<header class="sticky-header">Title</header>
<main class="article-content">...content...</main>

/* CSS */
.sticky-header {
  view-transition-name: header;
}

.article-content {
  view-transition-name: content;
}

/* Animate only content, header stays static */
::view-transition-old(content) {
  animation: fade-out 0.3s ease-out;
}

::view-transition-new(content) {
  animation: fade-in 0.3s ease-out;
}

@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

This enables complex patterns like:

  • Fixed header/footer, only main content animates
  • Hero images that zoom while text fades
  • Sidebar sliding in while main content shifts

Integration in SPAs: Angular, React, Vue

Angular (with rxjs and router events)

// service: view-transition.service.ts
import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class ViewTransitionService {
  constructor(private router: Router) {
    this.init();
  }

  private init() {
    this.router.events
      .pipe(filter(event => event instanceof NavigationEnd))
      .subscribe(async () => {
        if ('startViewTransition' in document) {
          await (document as any).startViewTransition(() => {
            // Angular has already updated the template
          });
        }
      });
  }
}

React (with useEffect and router)

// useViewTransition.ts
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

export function useViewTransition() {
  const location = useLocation();

  useEffect(() => {
    if ('startViewTransition' in document) {
      (document as any).startViewTransition(() => {
        // React has already re-rendered
      });
    }
  }, [location.pathname]);
}

// Usage in component
function ArticlePage() {
  useViewTransition();
  return 
{...}
; }

Cross-Document Transitions: MPA Navigation

For traditional sites with page-to-page navigation, the browser coordinates the transition automatically without JavaScript, but requires the server to signal support via HTTP header:

/* On the server (Nginx, Express, etc.) */
/* Add the HTTP header */
Link: </styles.css>; rel="preload"; as="style"

/* In the page CSS, assign names to regions */
.header {
  view-transition-name: page-header;
}

.article {
  view-transition-name: page-article;
}

/* Animations for cross-document */
::view-transition-old(page-article) {
  animation: slide-fade-out 0.4s ease;
}

::view-transition-new(page-article) {
  animation: slide-fade-in 0.4s ease;
}

SSR (Server-Side Rendering) and Considerations

The View Transitions API is client-side and fully compatible with SSR:

  • SSR rendering: The server renders initial HTML, no issues with View Transitions.
  • Client hydration: After Angular/React completes hydration, client-side navigation can trigger transitions.
  • First load: The first page doesn't animate (no "old" view). Transitions start from the second navigation.
// In SSR/hydration tools from Angular (server.ts)
// or React entry point, no special configuration is needed.
// The View Transitions API is completely transparent to SSR.

// However, you should still wrap navigation logic:
async handleNavigation(route: string) {
  if ('startViewTransition' in document) {
    await (document as any).startViewTransition(async () => {
      await this.router.navigate([route]);
    });
  } else {
    await this.router.navigate([route]);
  }
}

Progressive Enhancement and Fallback

The API is relatively new: Firefox and Safari are still adding full support. Fallback strategies are straightforward:

// Check support before using
if (document.startViewTransition) {
  // Use the View Transitions API
  await document.startViewTransition(updateDOM);
} else {
  // Fallback: update DOM without transition
  updateDOM();
}

// Or using feature detection in CSS
@supports (view-transition-name: auto) {
  /* CSS rules leveraging View Transitions */
  ::view-transition-old(root) {
    animation: slide-out 0.6s ease;
  }
}

/* If not supported, no animation */

Accessibility: prefers-reduced-motion

Not everyone wants animations. Always respect user preference via the prefers-reduced-motion media query:

/* Disable animations for motion-sensitive users */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none !important;
    opacity: 1 !important;
  }
}

// Or in JavaScript, skip transition entirely:
async handleNavigation(route: string) {
  const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

  if ('startViewTransition' in document && !prefersReduced) {
    await (document as any).startViewTransition(updateDOM);
  } else {
    updateDOM();
  }
}

Performance Tips and Common Issues

While the View Transitions API is generally efficient, consider these optimizations:

  • Snapshot cost: Capturing the page snapshot has a cost. On very large pages (100MB+ DOM), snapshot can take milliseconds. For normal sites, it's negligible.
  • Animation duration: Keep animations short (300-600ms). Long animations can feel like slowdowns.
  • Compositing layers: The transition creates a compositing layer. Avoid animating heavy will-change elements during the transition.
  • Debug flicker: If content "flickers", ensure DOM updates are inside the startViewTransition() callback.

Real-World Use Cases

The View Transitions API shines in scenarios like:

  • Blog/News sites: Transition between articles with automatic smooth transitions.
  • E-commerce: Navigate between product categories with fluid animation.
  • Admin dashboards: Tabs and pages that animate professionally.
  • SPA frameworks: Any Angular/React/Vue app that changes routes automatically benefits.

FAQ: Frequently Asked Questions

Which browsers support View Transitions?

Same-document (Baseline 2025): Chrome 111+, Edge 111+, Opera 97+, Firefox 119+. Safari support coming soon. Cross-document: Chrome 111+ and Edge 111+ today; universal support in 2026.

Can I use View Transitions with Next.js, Nuxt or other frameworks?

Yes. Any framework that supports client-side navigation (router) can use startViewTransition(). Some frameworks are adding built-in helpers (Next.js 13+).

Will it degrade my site's performance?

No. The snapshot and animation have negligible cost on normal pages. On huge pages (10MB+ DOM), snapshot cost is a few milliseconds. The API is optimized by the browser.

Can I control the duration and timing of the transition?

Completely. Use standard CSS properties animation-duration, animation-timing-function, etc. on the pseudo-elements.

Does it work with iFrames?

No. Transitions are scoped to the main document window. iFrames cannot trigger parent page transitions. For cross-frame communication, use PostMessage.

Conclusions

The View Transitions API represents a paradigm shift in web animations. For the first time, the browser itself manages coordination between snapshots, overlays, and timing, freeing developers from laborious JavaScript and delicate timing. With Baseline 2025 support for same-document and complete support in 2026 for cross-document, this API will become the de facto standard for smooth navigation in the coming years.

The recommended strategy: start using startViewTransition() in your SPAs right now (graceful fallback for legacy browsers), and prepare cross-document transitions for traditional MPA applications in 2026. Your site will have the look of a native app, without a single external library.

Try These Tools

Free developer tools related to this topic.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects