cross page transitions native in the browser
The View Transitions API brings something that used to require SPAs with heavy frameworks and turns it into a native browser feature: seamless animations when navigating between pages, with view-transition-name for shared elements, CSS pseudo-elements for full style control, and cross-document support for multi page apps.
Table of Contents
- 1. What the View Transitions API solves
- 2. Core principle: snapshot, DOM change, animation
- 3. ::view-transition pseudo-elements and CSS control
- 4. view-transition-name: animating shared elements
- 5. SPA integration: document.startViewTransition()
- 6. MPA cross-document transitions
- 7. Navigation API and transition types
- 8. MPA vs. SPA View Transitions compared
- 9. Accessibility and prefers-reduced-motion
- 10. Summary
- 11. FAQ
1. What the View Transitions API solves
Page transitions on the web have long been a compromise. SPAs built with React, Vue or Angular could implement smooth animations because they kept control of the DOM, but at the cost of complexity, bundle size and poor performance on initial load. Classic multi page apps (MPAs) had nothing but the hard page swap for decades: a white flash, then the new content appears. The View Transitions API closes that gap with an elegant, browser-native mechanism that works for both paradigms.
The core idea is surprisingly simple: the browser takes a screenshot of the current state, performs the DOM change or the page navigation, and then animates the transition between the old screenshot and the new state. Developers define how that transition should look via CSS, using view-transition-name for elements that should "fly across", and CSS pseudo-elements for control over timing and easing. The View Transitions API runs fully composited and never blocks the main thread during the animation.
2. Core principle: snapshot, DOM change, animation
The sequence of a View Transition is always the same, whether SPA or MPA. Step 1: the browser freezes the current rendering and creates snapshots for every element with a view-transition-name, plus one overall snapshot of the page. Step 2: the DOM change is carried out (synchronously for SPAs, by loading the new page for MPAs). Step 3: the browser creates new snapshots for the new DOM state. Step 4: the pseudo-element trees are built and the CSS-defined animations start.
During the animation, both the old and the new DOM state exist as snapshots at the same time, as layered pseudo-elements inside the ::view-transition tree. The snapshots are represented as ::view-transition-old and ::view-transition-new pseudo-elements. The browser stacks these pseudo-elements on top of each other and animates the transition. The elegant part: for the default transition (a cross-fade), you do not need to write a single line of CSS, the browser ships a clean crossfade effect out of the box.
/* The default view transition is a cross-fade, no CSS needed.
Override or extend with custom styles: */
/* Control the root (full-page) transition */
::view-transition-old(root) {
animation: 0.3s ease-in both fade-out;
}
::view-transition-new(root) {
animation: 0.3s ease-out both fade-in;
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* Slide transition instead of cross-fade */
@keyframes slide-out-left {
from { transform: translateX(0); }
to { transform: translateX(-100%); }
}
@keyframes slide-in-right {
from { transform: translateX(100%); }
to { transform: translateX(0); }
}
::view-transition-old(root) {
animation: 0.35s ease-in-out both slide-out-left;
}
::view-transition-new(root) {
animation: 0.35s ease-in-out both slide-in-right;
}
3. ::view-transition pseudo-elements and CSS control
During a transition, the View Transitions API creates a complete tree of pseudo-elements layered above the normal document rendering. The root pseudo-element is ::view-transition, which holds the entire transition state. Below it hang ::view-transition-group([name]) elements for each named transition participant. Each group contains a ::view-transition-image-pair([name]), and underneath that, ::view-transition-old([name]) and ::view-transition-new([name]) as the actual snapshot images.
These pseudo-elements are fully controllable with CSS. You can animate each participant independently, assign separate timing functions, or remove a participant from the transition entirely with animation: none. One important detail: the ::view-transition-group is automatically initialized with the position and size of the old element and animates toward the position and size of the new element, that is the mechanism behind the "flying" shared element animations. CSS properties such as animation-timing-function, animation-duration and animation-delay can be set directly on the pseudo-elements.
4. view-transition-name: animating shared elements
The most powerful feature of the View Transitions API is shared element animation via view-transition-name. When an element on page A and an element on page B share the same unique view-transition-name value, the browser animates the element seamlessly from its old position and size to its new position and size, without you having to calculate position, size or timing manually. The browser reads these values from the snapshots and computes a FLIP animation (First, Last, Invert, Play) automatically.
Critical constraint: a view-transition-name value must be unique in the document at every point in time. Two elements sharing the same name at the same moment in the DOM cause the entire transition to abort. This matters especially for list items that all represent the same type of content (for example product cards): here the name must be generated dynamically, for example with an ID-based suffix like view-transition-name: product-card-42. This value can be set via an HTML style attribute, via CSS custom properties, or via JavaScript.
/* Shared element: product image flies from list to detail page */
/* On product list page */
.product-card[data-id="42"] .product-image {
view-transition-name: product-image-42;
/* contain: layout; is automatically set for named elements */
}
.product-card[data-id="42"] .product-title {
view-transition-name: product-title-42;
}
/* On product detail page, same names */
.product-detail .hero-image {
view-transition-name: product-image-42;
}
.product-detail h1 {
view-transition-name: product-title-42;
}
/* Customize the flight animation for the image */
::view-transition-group(product-image-42) {
animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
animation-duration: 0.5s;
}
/* Fade the title separately with a slight delay */
::view-transition-old(product-title-42),
::view-transition-new(product-title-42) {
animation-duration: 0.25s;
animation-delay: 0.1s;
}
/* view-transition-name can also be set via inline style or JS:
element.style.viewTransitionName = `product-image-${id}` */
5. SPA integration: document.startViewTransition()
For single page apps, document.startViewTransition() is the JavaScript entry point. It takes a callback that performs the actual DOM change. The browser makes sure a snapshot of the current state is taken before the callback runs, and creates the new snapshot and starts the transition after the callback (once the returned promise resolves). The callback can be asynchronous, which matters for data fetches that need to complete before the DOM update happens.
The startViewTransition() function returns a ViewTransition object with the promises ready (transition ready), finished (transition complete) and updateCallbackDone (DOM update done). You can use ready to, for example, synchronize animations with the Web Animations API. The finished promise enables cleanup actions after the transition. With viewTransition.skipTransition() a running transition can be aborted immediately, useful for fast users who navigate again while a transition is still in progress.
6. MPA cross-document transitions
The original View Transitions API (Level 1) only supports same-document transitions, meaning transitions where JavaScript coordinates the DOM change. Level 2 brings cross-document transitions for multi page apps, with no JavaScript required. The feature is enabled in CSS with @view-transition { navigation: auto; }, this single line is enough for the browser to automatically use the View Transitions API when navigating between pages on the same origin.
Cross-document transitions work through the pageswap and pagereveal events fired on the window object. With pageswap you can prepare the outgoing page for the snapshot, with pagereveal the incoming page. Through event.viewTransition in both events you get access to the ViewTransition object and can adjust or skip animations. For navigation types (forward, backward, reload), the Navigation API supplies the information, so you can implement direction-aware animations.
7. Navigation API and transition types
The Navigation API and the View Transitions API are closely linked. The Navigation API lets you intercept navigation events and determine the transition type, forward navigation, backward navigation (back/forward), or reload. Different CSS animations can be triggered based on that type. The classic example: forward navigation shows a left-to-right slide, backward navigation shows the reverse slide. This used to be possible only with complex SPA routing libraries.
For MPA transitions, the transition type is set via the types option in startViewTransition() or via the pageswap/pagereveal event. In CSS, :active-view-transition-type() is used to define direction-aware animations. This approach fully replaces JavaScript-based routing for animation purposes, the routing business logic stays in server rendering, while the animation lives entirely in CSS.
/* MPA Cross-Document View Transitions, pure CSS approach */
/* Enable cross-document transitions for same-origin navigation */
@view-transition {
navigation: auto;
}
/* Default: cross-fade (built-in, nothing needed) */
/* Direction-aware transitions using :active-view-transition-type() */
:root:active-view-transition-type(forward) {
&::view-transition-old(root) {
animation: slide-out-to-left 0.35s ease-in-out both;
}
&::view-transition-new(root) {
animation: slide-in-from-right 0.35s ease-in-out both;
}
}
:root:active-view-transition-type(backward) {
&::view-transition-old(root) {
animation: slide-out-to-right 0.35s ease-in-out both;
}
&::view-transition-new(root) {
animation: slide-in-from-left 0.35s ease-in-out both;
}
}
@keyframes slide-out-to-left { to { transform: translateX(-100%); } }
@keyframes slide-in-from-right { from { transform: translateX(100%); } }
@keyframes slide-out-to-right { to { transform: translateX(100%); } }
@keyframes slide-in-from-left { from { transform: translateX(-100%); } }
/* Respect user motion preferences */
@media (prefers-reduced-motion: reduce) {
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.01ms !important;
}
}
8. MPA vs. SPA View Transitions compared
The choice between MPA and SPA View Transitions depends on the project's architecture. MPA cross-document transitions (Level 2) are the simpler solution for server-rendered pages: no JavaScript, just @view-transition { navigation: auto; } and CSS. SPA transitions with startViewTransition() offer more control, but require JavaScript to coordinate every DOM change.
| Feature | SPA (Level 1) | MPA (Level 2) | Recommendation |
|---|---|---|---|
| JavaScript required | Yes (startViewTransition) | No (CSS only) | MPA for SSR projects |
| Shared elements | Yes | Yes (Chrome 126+) | Both supported |
| Browser support 2026 | Chrome 111+, FF 136+ | Chrome 126+, FF 136+ | Progressive enhancement |
| Direction-aware animations | Via JS + types | Via pageswap/CSS types | MPA more elegant |
| Data fetch before transition | Async callback | Not possible | SPA for dynamic content |
9. Accessibility and prefers-reduced-motion
Every View Transition implementation must respect prefers-reduced-motion. Users who have motion reduction enabled can experience nausea or disorientation from intense page transition animations. The correct implementation: inside an @media (prefers-reduced-motion: reduce) block, all view transition animations are either disabled or reduced to a simple fade. An animation-duration: 0.01ms on the ::view-transition-old and ::view-transition-new pseudo-elements effectively eliminates any motion.
Beyond that, you should make sure View Transitions never hide or delay content information. Screen readers must not be blocked by active transitions, current browser implementations handle this correctly, but it should still be explicitly tested for complex asynchronous transitions in SPA contexts. The finished promise of the ViewTransition object can be used to set focus correctly once the transition completes.
Mironsoft
View Transitions, Hyvä theme development and modern CSS architectures
Want View Transitions implemented in Magento and Hyvä?
We integrate the View Transitions API into Hyvä themes and Magento frontends, for seamless product page transitions, animated category navigation and an app-like user experience without any JavaScript framework overhead.
MPA transitions
@view-transition for Magento MPA, no JavaScript, purely CSS-based page transitions
Shared elements
Product images and titles fly seamlessly from the listing page to the detail page
Accessibility
prefers-reduced-motion, focus management and WCAG-compliant transition implementation
10. Summary
The View Transitions API is one of the most significant frontend innovations of recent years. It enables seamless page transitions, including animated shared elements, for both single page apps and multi page apps. view-transition-name identifies the elements that should "fly across". The CSS pseudo-elements ::view-transition-old() and ::view-transition-new() give full control over timing and easing. For MPA projects, @view-transition { navigation: auto; } is enough to enable cross-document transitions, with no JavaScript at all.
The progressive enhancement approach is clear: in non-supporting browsers, a normal page navigation happens, fully functional. In supporting browsers (Chrome 111+/126+ for MPA, Firefox 136+), the animation runs. prefers-reduced-motion must be respected in every implementation. For Hyvä themes and Magento MPA frontends, the MPA variant is the most direct solution: minimal CSS, no framework dependencies, maximum performance.
View Transitions API: the essentials at a glance
Enable MPA
@view-transition { navigation: auto; }, one CSS line enables cross-document transitions for same-origin navigation.
Shared elements
view-transition-name: unique-name on the old and new element. The browser calculates the FLIP animation automatically.
CSS control
::view-transition-old(name) and ::view-transition-new(name) for individual keyframe animations. ::view-transition-group for position animation.
Accessibility
@media (prefers-reduced-motion: reduce) with animation-duration: 0.01ms for all ::view-transition pseudo-elements. Mandatory.