To appreciate the importance of this API, it’s helpful to review the evolution of web architectures.
In the early days of the web, the MPA model was the only one. Browsers were relatively simple clients with less powerful JavaScript engines. Their main job was to render HTML and CSS sent from the server.
Consequently, the entire application state (who you are, what’s in your cart, etc.) had to reside and be managed almost exclusively on the server. With each interaction, the browser requested a completely new page.
With the evolution of browsers and the growing power of JavaScript, the SPA model emerged. In this architecture, a significant portion of the application state is delegated to the client. The HTML page is loaded only once, and subsequent interactions dynamically update the view using JavaScript, creating a fluid and responsive user experience.
An SPA architecture, where the base page is persistent, allows for free manipulation of the resources allocated by the browser for that tab. Animating an element from state A to state B is therefore a native and relatively simple operation.
In an MPA, every time you navigate to a new page, the context of the previous page is completely destroyed. All variables, DOM elements, and in-memory states are discarded to make way for the new environment.
This makes it impossible to use JavaScript to animate an element between the two pages.
Although mechanisms like localStorage
, sessionStorage
, and cookies
allow data to persist across navigations, they don’t solve the animation problem. They persist data, not DOM elements, during the brief moment of transition between one page rendering and the next. This is an intrinsic limitation of the browser’s navigation model—a sandbox whose rules we cannot bend.
The View Transitions API was created to overcome this exact limitation. Introduced in Chrome 111 (March 2023) and decently supported in most browsers, it allows you to orchestrate animated transitions even between different documents in an MPA.
With just a few lines of CSS, you can instruct the browser to handle the transition of specific elements.
The first step is to enable the feature for cross-document navigations. This is done by adding a simple rule in the CSS of both pages (the source and the destination).
/* style.css */
@view-transition {
navigation: auto;
}
This rule tells the browser to intercept same-origin navigations and apply a default transition (a cross-fade).
The key step is to tell the browser which element on page A corresponds to which element on page B. This is achieved by assigning the same unique value to the CSS view-transition-name
property on both elements.
Listing Page (/blog
):
<a href="/blog/my-post">
<img
src="/path/to/thumbnail.jpg"
style="view-transition-name: hero-image-post-123;"
/>
</a>
Post Page (/blog/my-post
):
<img
src="/path/to/hero-image.jpg"
style="view-transition-name: hero-image-post-123;"
class="hero-image"
/>
By assigning the same view-transition-name
(hero-image-post-123
), the browser understands that these two elements are conceptually the same and will animate the transition between their different sizes and positions, creating a fluid and professional effect that was previously exclusive to SPAs.
Notice how the name hero-image-post-123
is specific. In a real application, this value wouldn’t be static but dynamically generated, for example, using the unique ID of the product or article (e.g., hero-image-post-${post.id}
). This ensures that each element in the list correctly and unambiguously points to its counterpart on the destination page.
Warning: The
view-transition-name
property must be unique in the DOM at any given time. If two visible elements share the same name, the browser won’t know how to handle the transition, and the animation will fail.Dynamic generation based on IDs solves this exact problem. You can create the
view-transition-name
value dynamically:<div style="view-transition-name: hero-image-post-${post.id};"></div>
By default, the browser applies a cross-fade. However, you have full control over the animation via CSS. Using special pseudo-elements, you can define complex and custom animations.
For example, to change the default page animation to a slide, you could use:
::view-transition-old(root) {
animation: slide-from-right 0.5s ease-out;
}
::view-transition-new(root) {
animation: slide-to-left 0.5s ease-in;
}
/* And you can specifically animate the shared element! */
::view-transition-new(hero-image-post-123) {
/* The transform animation (scale, position)
is handled by the browser. Here you can add more,
like a transition on the `border-radius`. */
transition: border-radius 0.5s;
}
This opens up infinite creative possibilities that go far beyond the standard effect.