CSS View Transitions API: Cross Page Transitions with view-transition-name
AI generated
CSS · Browser APIs · Animations · Navigation
CSS View Transitions API
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.

15 min read view-transition-name · ::view-transition · MPA · SPA · Navigation API Chrome 111+ · Chrome 126+ (MPA) · Firefox 136+

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.

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.

11. FAQ: CSS View Transitions API

1What is the View Transitions API?
Browser API for seamless animations when the DOM or the page changes. Snapshot before and after the change, animation controllable via CSS pseudo-elements.
2How do I enable MPA transitions?
@view-transition { navigation: auto; }, one CSS line. The browser automatically uses View Transitions on same-origin navigation. No JavaScript needed.
3What is view-transition-name?
CSS property for shared elements. Same unique name on the old and new page triggers the browser to animate a FLIP transition automatically from the old to the new position and size.
4Two elements with the same name?
The transition aborts. Names must be unique. For lists, generate them dynamically with an ID, 'card-' + item.id.
5Direction-aware slide animation?
:active-view-transition-type(forward/backward) in CSS. Different keyframes for ::view-transition-old and ::view-transition-new depending on direction.
6Browser support in 2026?
SPA: Chrome 111+, Firefox 136+, Safari 18+. MPA: Chrome 126+, Firefox 136+. Progressive enhancement via @supports.
7prefers-reduced-motion?
@media (prefers-reduced-motion: reduce): animation-duration: 0.01ms !important on ::view-transition-old and ::view-transition-new. Mandatory for accessibility.
8Async startViewTransition()?
Callback can return a promise. Browser waits for resolution (data fetch), then takes a new snapshot and animates. Ideal for SPA data fetches before a DOM update.
9Dynamic list names?
element.style.viewTransitionName = 'card-' + item.id, set once at render time. Do not set it during the transition, to guarantee uniqueness.
10::view-transition-old and -new?
Pseudo-elements for the snapshot images. Animatable with CSS keyframes, for individual timing, easing and transforms per named transition participant.