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-transitionand::view-transition-old/newpseudo-elements - Apply
view-transition-namefor 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-transitionHTML attribute and server/browser coordination.
Same-Document: Basics with startViewTransition()
To animate a same-document transition, the flow is straightforward:
- Wrap your DOM update inside
document.startViewTransition(() => { ... }) - The browser captures a snapshot of the current state (old view)
- The callback updates the DOM (new view)
- The browser creates an overlay pseudo-element
::view-transition - 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.

Commenti
Caricamento commenti...
Sign in to comment