View Transitions API — Production Page Transition Animations Without Libraries, After Achieving Baseline 2025
Honestly, my first reaction when I saw this API was "this actually works?" The idea of implementing app-like page transitions with just a few lines of CSS and a single JavaScript method — no Framer Motion, no GSAP — seemed too good to be true. But after using it in practice, it really does work. Because the browser handles the before-and-after snapshots at the compositor layer, the transform·opacity-based animation segments produce surprisingly smooth results.
In October 2025, with the release of Firefox 144, all four major browsers — Chrome, Edge, Safari, and Firefox — now support document.startViewTransition(). This marks the point where it can be used in production without polyfills or "Chrome only" comments. That's exactly why I'm writing this. If you're looking to introduce transition animations to an existing MPA for the first time, or if you want to drop a library dependency like Framer Motion, now is exactly the right time.
This article covers everything from how the API works, to framework-specific integration patterns, to the pitfalls you're likely to fall into in real-world use. Whether you're working in a SPA, MPA, Next.js, Astro, or SvelteKit environment, everything here is ready to apply directly.
Core Concepts
Baseline Newly Available: A status jointly defined by the W3C and browser vendors. It means the feature works in the latest versions of Chrome, Edge, Safari, and Firefox — the official milestone for "safe to use." The View Transitions API (same-document) reached this status in October 2025.
The Three Phases the Browser Uses to Handle a Transition
The mechanism is simpler than you might think. Rather than directly manipulating the DOM like complex animation libraries, the browser takes a snapshot of the state "before" and "after," then fills in the gap.
document.startViewTransition(callback)is called → the browser takes a snapshot of the current screen- DOM changes are performed inside
callback - The browser transitions from the old snapshot → new DOM using either the default crossfade or a custom CSS animation
The default is a crossfade, and you can customize it with CSS however you like.
SPA Mode vs. MPA Mode
The API is divided into two primary modes.
Same-document (SPA mode): You call document.startViewTransition() directly from JavaScript. As of October 2025, it is fully supported in Chrome 111+, Edge 111+, Safari 18+, and Firefox 144+. It is Baseline Newly Available and suitable for production use.
Cross-document (MPA mode): Applies transition animations between two pages of the same origin using only the CSS @view-transition at-rule. No JavaScript required at all. Supported in Chrome 126+, Edge 126+, and Safari 18.2+, but Firefox does not yet support it.
/* Just add these two lines to both pages */
@view-transition {
navigation: auto;
}Shared Element Transitions
I didn't believe this could be this easy at first, but this is where the real magic of the View Transitions API lies. By assigning a name to a specific element with the view-transition-name CSS property, that element moves independently during a page transition. This is the effect where an image smoothly flies from a product card to the product detail page.
/* Card image on the list page */
.product-card img {
view-transition-name: product-hero;
}
/* Hero image on the detail page */
.product-detail .hero {
view-transition-name: product-hero;
/* Prevents stretching when the image aspect ratio differs */
object-fit: cover;
}
/* Customize the transition animation */
::view-transition-old(product-hero),
::view-transition-new(product-hero) {
animation-duration: 0.4s;
animation-timing-function: ease-in-out;
}When elements with the same view-transition-name are found on both pages, the browser automatically interpolates between them — position, size, and shape included.
Good to Know — Level 2 Specification
The Level 2 specification currently under active development by the W3C contains features that address pain points felt in real-world use. These aren't features you can use right now, but it's helpful to understand where the API is headed.
view-transition-class: While view-transition-name is a unique ID per element, view-transition-class assigns a shared class to multiple elements so the same animation style can be applied to all of them at once.
Automatic name generation for repeated elements: A feature is being discussed that would have the browser automatically generate internal names for repeated elements like card lists. Values such as match-element and auto are under consideration, though nothing is finalized yet. For now, you must manually assign unique names to each dynamically generated list item.
:active-view-transition-type() pseudo-class: Allows you to conditionally apply different CSS animations depending on the transition type. The example below includes both a version using CSS nesting syntax (the & selector) and an expanded version.
/* Using CSS nesting syntax */
:active-view-transition-type(slide-left) {
&::view-transition-old(root) { animation-name: slide-out-left; }
&::view-transition-new(root) { animation-name: slide-in-right; }
}
/* Same effect without CSS nesting */
:active-view-transition-type(slide-left)::view-transition-old(root) {
animation-name: slide-out-left;
}
:active-view-transition-type(slide-left)::view-transition-new(root) {
animation-name: slide-in-right;
}Practical Application
Page Transition Animations in an MPA Without JavaScript
This is the fastest way to see results. In Astro or a traditional multi-page app, you don't need to touch a single line of JavaScript.
/* Add to <head> or global CSS on every page */
@view-transition {
navigation: auto;
}
/* Replace the default crossfade with a slide transition */
@keyframes slide-in-from-right {
from { transform: translateX(30px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes slide-out-to-left {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(-30px); opacity: 0; }
}
::view-transition-old(root) {
animation: 300ms ease-in-out slide-out-to-left;
}
::view-transition-new(root) {
animation: 300ms ease-in-out slide-in-from-right;
}| Component | Description |
|---|---|
@view-transition { navigation: auto; } |
Automatically activates transitions on same-origin page navigation |
::view-transition-old(root) |
Animation for the departing page (previous snapshot) |
::view-transition-new(root) |
Animation for the arriving page (new DOM) |
root |
Targets the entire page. Can be replaced with a specific element name |
Firefox users will experience normal page navigation without animation. The functionality itself works correctly, so this is fine from a Progressive Enhancement perspective.
Framework-Specific Integration Patterns
Here are the most commonly used patterns. Integration approaches differ quite a bit between frameworks.
Astro: Declare <ViewTransitions /> once in your layout component and you're done. This is the most natural integration — you keep the MPA structure while getting SPA-like transitions.
---
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>Next.js (App Router): Adding a flag to next.config.js automatically triggers startViewTransition on <Link> clicks. Supported in Next.js 15+. Pairing it with React 19.2's <ViewTransition> component allows for finer-grained control.
// next.config.js (Next.js 15+)
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
viewTransition: true,
},
};
export default nextConfig;The key with the <ViewTransition> component is that the same name must be used on both the list and detail sides for the shared element transition to connect.
// List page
import { ViewTransition } from 'react';
function ProductCard({ product }) {
return (
<ViewTransition name={`product-${product.id}`}>
<img src={product.image} alt={product.name} />
</ViewTransition>
);
}
// Detail page — the name must match for the two elements to connect as one transition
function ProductDetail({ product }) {
return (
<ViewTransition name={`product-${product.id}`}>
<img src={product.image} alt={product.name} className="hero" />
</ViewTransition>
);
}SvelteKit: There's no official built-in support, but a pattern using the onNavigate hook is well-established in the community. The reason for returning a Promise is to explicitly signal to the browser when the transition is complete. Without this structure, SvelteKit has no way of knowing when to finish the navigation.
// +layout.svelte or +layout.js
import { onNavigate } from '$app/navigation';
onNavigate((navigation) => {
// Pass through silently in unsupported browsers
if (!document.startViewTransition) return;
// Return a Promise so the browser knows when the transition is complete
return new Promise((resolve) => {
document.startViewTransition(async () => {
resolve();
await navigation.complete;
});
});
});Motion Handling with Accessibility in Mind
Skipping this will almost certainly get flagged in a real-world project. The browser does not automatically respect prefers-reduced-motion, so developers must handle it manually. It can cause discomfort for users with vestibular disorders or motion sensitivity, and it's a commonly cited issue in accessibility audits.
/* Recommended to include in global CSS from the start */
@media (prefers-reduced-motion: reduce) {
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}Pros and Cons
Advantages
| Item | Details |
|---|---|
| Eliminates library dependencies | Works with native CSS animations alone — no Framer Motion or GSAP needed |
| Compositor layer processing | transform·opacity transitions are handled by the GPU for smooth animation |
| Progressive Enhancement | Unsupported browsers navigate normally. Can branch on document.startViewTransition existence |
| MPA support | Apply page transition animations via the @view-transition at-rule without a client-side router |
| No layout shift | The snapshot is already taken before DOM changes, so layout doesn't flicker during transitions |
| Baseline achieved | Supported in the latest versions of all four major browsers since October 2025 (same-document) |
Of these, the one that makes the biggest practical difference is MPA support. Not needing to add a client-side router makes a substantial difference in Astro or existing server-rendered app projects.
Compositor Layer: A layer handled directly by the GPU in the browser's rendering pipeline. When properties like
transformandopacityare processed at this layer, they don't touch CPU layout calculations, resulting in much smoother performance. The View Transitions API leverages these properties by default when transitioning between snapshots, which is why it has a performance advantage.
Disadvantages and Caveats
| Item | Details | Mitigation |
|---|---|---|
| No concurrent transitions | Only one transition can run per document. If a new transition fires during an existing one, the previous one is skipped | Consider a queue structure that triggers the next transition after the current one completes |
Duplicate view-transition-name |
If two or more elements share the same name on the same screen, the entire transition is skipped | Assign unique ID-based names to dynamic lists (improvement planned for Level 2) |
| Callback blocking | Users see the previous page frozen while the callback hasn't resolved | Avoid heavy data fetching inside the callback. Load data first, then start the transition |
| Accessibility issues | Screen readers may announce both old and new DOM content simultaneously; focus can become confused during transitions | Manage ARIA live regions and explicitly move focus after the transition completes |
prefers-reduced-motion not handled automatically |
The browser does not automatically apply reduced-motion settings | Must manually write @media (prefers-reduced-motion: reduce) in CSS |
| Firefox cross-document not supported | Firefox falls back to normal navigation when @view-transition is used in an MPA |
Handle via Progressive Enhancement. Under discussion for Interop 2026 |
The Most Common Mistakes in Real-World Use
-
Duplicate
view-transition-nameon dynamic lists: If you don't generate names based on an ID — likeproduct-1,product-2— the moment two elements with the same name appear, the entire transition silently skips. This is the kind of thing that can have you scratching your head for a while trying to figure out why it's not working. -
Calling
fetchinside the callback: While the data is being fetched, the previous page is frozen and shown to the user as-is. It's better to finish loading the data first, then start the transition. -
Omitting
prefers-reduced-motionhandling: Building the habit of including this in your global CSS from the start means you never have to think about it later. It's also a frequently cited issue in accessibility audits.
Closing Thoughts
The View Transitions API is no longer an "experimental feature" — it is a proven, browser-native tool ready for production use without any library. So where can you start right now?
-
If you have an MPA project: Try adding the two lines
@view-transition { navigation: auto; }to your global CSS. You'll immediately see a default crossfade applied to every page navigation, with no additional JavaScript required. -
If you're on a SPA or framework project: For Next.js, start by adding
experimental: { viewTransition: true }tonext.config.js. For Astro, try adding the<ViewTransitions />component to your layout. For SvelteKit, paste theonNavigatepattern above into+layout.svelte. -
If you want the most impressive effect: If you have a repeating card UI — like a product list leading to a detail view — assign the same
view-transition-nameto the card image and the detail page hero image. The moment it first works, you'll understand the excitement firsthand.
References
- View Transition API - MDN Web Docs — Basic API reference; a good starting point when first approaching this
- Using the View Transition API - MDN — Step-by-step implementation details
- What's new in view transitions (2025 update) - Chrome for Developers — Summary of 2025 changes from the Chrome team
- Smooth transitions with the View Transition API - Chrome for Developers — Official implementation guide with abundant examples
- Misconceptions about view transitions - Chrome for Developers — Common misconceptions; useful when making the case to your team
- CSS View Transitions Module Level 2 - W3C Draft — Level 2 specification draft; check syntax before it's finalized
- React Labs: View Transitions, Activity, and more - React Official Blog — Original React
<ViewTransition>announcement - React
<ViewTransition>Official Docs — Component API reference - next.config.js: viewTransition - Next.js Official Docs — Next.js 15+ integration setup
- View transitions - Astro Official Docs — Advanced Astro patterns including
persistandanimatedirectives - Animating Page and View Transitions with Accessibility in Mind — In-depth accessibility handling including ARIA and focus management
- Can I use: View Transitions API — Real-time current browser support status