A layout plan for a web page.

Smooth and simple page transitions with the shared element transition API

Published on Updated on

When you navigate around apps on your phone, there's usually some sort of transition, from a simple fade or slide from one screen to another, to more complex transitions that move different elements independently:

Although, on the web, we don't tend to get those kinds of effects. Creating a transition from one page to another after clicking a link is, well, impossible. But, it isn't exactly easy within a single-page app either.

Even in a simple case, where content fades from one screen to another, it means rendering your page with both states at once, performing a cross-fade, then removing the old state.

This is harder than it sounds. You need to make sure that the outgoing page can't receive additional interactions, and that the outgoing state doesn't jank the transition by updating its own state. You also need to ensure the presence of both states doesn't create a confusing experience for those using accessibility technology.

Platforms like Android and iOS have tools to make this stuff easier, so why can't we? That's where the shared element transitions API comes in! It's a proposal we're working on that provides a high-level way to transition between page states.

Here it is in action in a remix of the Preact website:

Here's the live version of the demo.

How to use shared element transitions

Enable #document-transition in about:flags to experiment locally without an origin trial token.

Enabling support during the origin trial phase

Starting in Chromium 92, the shared element transition API is available as an origin trial in Chromium. The origin trial is expected to end in Chromium 94.

Origin trials allow you to try new features and give feedback on their usability, practicality, and effectiveness to the web standards community. For more information, see the Origin Trials Guide for Web Developers. To sign up for this or another origin trial, visit the registration page.

Register for the origin trial

  1. Request a token for your origin.
  2. Add the token to your pages. There are two ways to do that:
    • Add an origin-trial <meta> tag to the head of each page. For example, this may look something like:

      <meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">

    • If you can configure your server, you can also add the token using an Origin-Trial HTTP header. The resulting response header should look something like:

      Origin-Trial: TOKEN_GOES_HERE

Feature detection

The presence of documentTransition on document indicates support for this API.

if ('documentTransition' in document) {
// Feature supported


The transitions have two forms, simple transitions, and more complex "shared element" transitions.

Simple transitions

// When the user clicks on a link/button:
async function navigateToSettingsPage() {
// Capture the current state.
await document.documentTransition.prepare({
rootTransition: 'cover-left',

// This is a function within the web app that updates the DOM:

// Start the transition.
await document.documentTransition.start();
// Transition complete!

To perform transitions, you:

  1. Call the prepare() function to capture the current visual state.
  2. Update the DOM to represent the new page.
  3. Call start() to perform the transition.

rootTransition controls the style of the transition.

Here's a live demo of the styles that are currently available.

Limitations of simple transitions

Animations suddenly stop on the outgoing DOM. The outgoing DOM is captured as a single frame, meaning that things like gifs and CSS animations will freeze. This is by design, and unlikely to change. This only impacts the outgoing DOM; the new DOM will animate as usual.

The transition applies to the whole document. You can't limit the transition to some inner-UI. This feature may be added in future.

The naming is currently directional, for example, cover-left. This will be changed to match CSS logical properties, so start and end will be used rather than left and right, and inline and block will be used to refer to the axis, allowing the API to react to the writing mode of the page.

Control over the transition is limited. You can't control the length or other properties of the transition. This will change in future versions of the API, although this will always be limited to things that can be done with a screenshot of state, such as transforms, opacity, and crops.

Shared element transitions

Shared element transitions build on top of simple transitions by letting you specify elements to transition independently. This is how the header in the following example stays static during these transitions:

But they don't need to stay static. They can also move independently of the root transition.

Here's how it's done:

// When the user clicks on a link/button:
async function navigateToSettingsPage() {
// Capture and visually freeze the current state.
await document.documentTransition.prepare({
rootTransition: 'cover-up',
sharedElements: [element1, element2, element3],
// This is a function within the web app:
// Start the transition.
await document.documentTransition.start({
sharedElements: [element1, element4, element5],
// Transition complete!

sharedElements is the difference here, both in the prepare step and the start step.

In this case, element1 will animate independently from the rest of the page, moving from its original position and size to its end position and size (or staying still if it doesn't move).

The elements don't need to be the same node. In this case, element2 will transition to element4, because they're each second in the provided array.

Limitations of shared element transitions

When an element becomes 'shared', it shouldn't render on the background too, but right now it does. This creates "ghosting" effects on elements that should appear static. This has been fixed, but hasn't made it to stable yet.

Shared transition elements will render on top of other elements. If other elements render on top of the transitioned elements, they'll pop underneath for the transition. We're not sure yet if this should be a general limitation, or if it should be fixed.

Shared transition elements must have contain: paint. This makes it difficult to transition elements that have paint effects outside their content rectangle, such as shadows, blurs, and outlines. Hopefully this will change in future.

Shared transition elements cannot have background effects. Things like mix-blend-mode and backdrop-filter won't interact correctly with the background during a transition.

Control over the transition is limited. Right now, when one element transitions to another, their "screenshots" quickly cross-fade while they scale and translate into position. Future versions of the API will add additional configuration, but this will always be limited to things that can be done with a screenshot of state, such as transforms, opacity, and crops.

In future: Multi-page apps

Transitions between pages is currently impossible, but this API intends to fix that.

It's currently unclear how the API would look, but you can follow along with the discussion on GitHub.

In future-er: Multi-page cross-origin apps

The cross-origin story is more complicated. Even though a page transition has a limited timeframe, it would still be giving one site brief visual control over another, which is risky. Cross-origin page transitions will either come with restrictions, or require some sort of agreement between the two origins, perhaps using headers.

For more details on the problem, and to follow along with discussions, see the GitHub issue.


We're looking for feedback on the direction of this API.

Tell us what you think about the feature

Does it do what you need? Is it missing something important? Please let us know your thoughts in the GitHub repo for the feature.

Report problems

Did you find a bug with Chromium's implementation? File a bug at new.crbug.com. Be sure to include as much detail as you can, simple instructions for reproducing, and mention "shared element transitions" in your bug report. Glitch works great for sharing quick and easy test cases.


Hero image by Sigmund on Unsplash.

Last updated: Improve article

We serve cookies on this site to analyze traffic, remember your preferences, and optimize your experience.