Headline: The View Transitions API animates the change between two DOM states — you call
document.startViewTransition(), and the browser cross-fades or morphs the old page into the new one. In a Next.js App Router app I added smooth page and element transitions with a few lines of CSS and no animation library, and the only real trap was giving two elements the sameview-transition-name.
Key takeaways
- The View Transitions API animates between two DOM states via
document.startViewTransition(updateDOM); the browser snapshots the page before and after your callback and animates the difference. - The default transition is a full-page cross-fade named
root; assignview-transition-nameto any element to animate it independently. - For Next.js App Router client navigations, wrap
router.pushindocument.startViewTransition, or setexperimental.viewTransitionto use React's<ViewTransition>component. - React's
<ViewTransition>is still experimental in React 19.x — opt-in behind a flag, not stable. - A duplicate
view-transition-namein one snapshot makes the browser skip the transition; every name must be unique per snapshot.
What is the View Transitions API, and what problem does it solve?
The View Transitions API is a browser API that animates the visual change between two states of the DOM. Before it, animating a route change meant keeping both the old and new UI in the DOM at once, coordinating CSS classes, and cleaning up after — a library's whole job. The API replaces that: you call document.startViewTransition(callback), the browser screenshots the current page, runs your callback to update the DOM, screenshots the new state, and cross-fades between the two snapshots. Same-document (SPA) transitions shipped in Chrome 111 in March 2023; cross-document (MPA) navigations shipped in Chrome 126 in June 2024, and Firefox and Safari have since added the same-document API.
How do I run my first view transition?
Call document.startViewTransition() with a callback that mutates the DOM. The default animation is a quarter-second cross-fade of the whole page.
function update() {
document.body.dataset.theme = 'dark';
}
if (document.startViewTransition) {
document.startViewTransition(update); // animated
} else {
update(); // fallback: no animation
}
Feature-detect first — document.startViewTransition is undefined without support, and calling the update directly is the correct fallback. The method returns a ViewTransition object with three promises: .updateCallbackDone, .ready, and .finished. Await .finished to run code after the transition.
How do I animate one element independently?
Give the element a unique view-transition-name and the browser animates it as its own group instead of folding it into the page cross-fade — this is the shared-element effect where a thumbnail grows into a hero image.
.hero-image { view-transition-name: hero; }
::view-transition-old(hero),
::view-transition-new(hero) { animation-duration: 0.4s; }
During a transition the browser builds pseudo-elements: ::view-transition-old(hero) is the outgoing snapshot, ::view-transition-new(hero) the incoming one, and ::view-transition-group(hero) wraps both and animates position and size. The name root is assigned to the whole document, so ::view-transition-old(root) targets the full-page cross-fade.
How do I add view transitions to Next.js App Router navigation?
Wrap the navigation in document.startViewTransition so the DOM update React performs during the route change becomes the transition's callback.
'use client';
import { useRouter } from 'next/navigation';
export function useViewTransitionRouter() {
const router = useRouter();
return (href: string) => {
if (!document.startViewTransition) return router.push(href);
document.startViewTransition(() => router.push(href));
};
}
For cross-document transitions in a static or MPA-style site, skip JavaScript and opt in with CSS in both pages:
@view-transition { navigation: auto; }
The community next-view-transitions package wraps the client pattern if you would rather not write the hook.
What is React's <ViewTransition> component, and is it ready?
<ViewTransition> is an experimental React component that animates its children when they enter, exit, or reorder from a transition or a resolving Suspense boundary. Instead of calling document.startViewTransition yourself, you wrap the changing UI and React drives the API.
import { unstable_ViewTransition as ViewTransition } from 'react';
<ViewTransition>
<ProductList items={items} />
</ViewTransition>
It is still experimental in React 19.x — the import is prefixed unstable_, and in Next.js you enable it with experimental.viewTransition: true. I ship the plain document.startViewTransition approach in production because it is a stable browser API, and keep <ViewTransition> for prototypes.
What breaks — duplicate names, reduced motion, and performance?
The failure I hit most: two elements sharing one view-transition-name in the same snapshot. The browser cannot decide which to morph, so it skips the transition and logs a warning. For a list, name only the item that is actually transitioning, using a unique id.
| Problem | Cause | Fix |
|---|---|---|
| Transition doesn't animate | duplicate view-transition-name in one snapshot |
make each name unique per snapshot |
| Motion for users who opted out | API always animates | disable animation inside prefers-reduced-motion: reduce
|
| Dropped frames on low-end devices | large full-screen snapshots | limit named transitions to elements that must move |
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) { animation: none !important; }
}
The old and new states render as snapshots and the group moves them with transforms, which the compositor handles cheaply — but keep independently-named transitions to the elements that genuinely need to move.
FAQ
Q: Which browsers support the View Transitions API?
A: Same-document transitions shipped in Chrome 111 (March 2023) and are now in Firefox and Safari; cross-document transitions shipped in Chrome 126 (June 2024). Always feature-detect document.startViewTransition.
Q: Do I need a library for view transitions in Next.js?
A: No. Wrapping router.push in document.startViewTransition covers client navigations with no dependency. next-view-transitions just packages that pattern.
Q: Why did my view transition not animate?
A: Usually two elements share the same view-transition-name in one snapshot, which makes the browser skip the transition. Names must be unique per captured state.
Q: Is React's <ViewTransition> component stable?
A: No. As of React 19.x it is experimental, imported as unstable_ViewTransition and enabled behind experimental.viewTransition. Use the browser API directly in production.
Q: Can view transitions animate width and height?
A: Yes, via ::view-transition-group, which animates size and position between snapshots. Keep heavier animation to transform and opacity, since the old and new states are rendered as images.
Originally published on devya.dev. Also on eng-ahmed.com. Built by Devya Solutions.
Top comments (0)