The View Transitions API animates between two states of a page, and for full page navigations on a traditional multi-page site it requires no JavaScript at all. Two lines of CSS on both pages is the entire setup. Cross-document transitions work in Chromium browsers since Chrome 126 and in Safari 18.2, with Firefox support in progress. Same-document transitions have wider support: Chrome 111, Safari 18, and Firefox 144.

I built a whole single page app once, years ago, mostly so that navigating between pages would not flash white. That was the actual reason. This API makes that reason obsolete.

Step 1: Opt both pages in

For cross-document transitions there is no function to call. Both the page you leave and the page you arrive at have to opt in through CSS, and the navigation must be same-origin:

Put that in a stylesheet loaded by every page. That is it. Navigate between two pages on your site and you get a cross-fade instead of a white flash. If either page is missing the rule, nothing happens and you get the normal navigation, which is a reasonable failure mode.

Step 2: Name the elements that should morph

A cross-fade is fine, but the interesting part is elements that persist across the navigation. Give the same view-transition-name to an element on both pages and the browser animates between their two positions and sizes:

Click a card, and its thumbnail flies out and grows into the hero image on the next page. No JavaScript, no shared layout component, no framework.

One hard constraint that catches everyone: a view-transition-name must be unique on the page at the moment the transition starts. Put the same name on every card in a list and the transition silently fails. You have to set it dynamically on the specific card that was clicked:

Yes, that is JavaScript, in a post that promised none. The two-line version genuinely needs none. The moment you want element morphing out of a list, you need this bit.

Step 3: Customize the animation

The browser generates pseudo-elements you can target with ordinary CSS animations. The useful ones are ::view-transition-old() for the outgoing state and ::view-transition-new() for the incoming one:

The root keyword targets the whole page. Swap in your own transition name to animate a specific element differently from everything else.

Step 4: Respect reduced motion

This is not optional and it is easy to forget, because the default cross-fade is subtle enough that it does not feel like motion to someone who is not sensitive to it:

A morphing element flying across the screen is exactly the kind of motion that causes problems for people with vestibular disorders. Ship the media query with the feature, not later.

Same-document transitions

If you are updating the DOM without navigating, the API is a single function that takes a callback:

The browser screenshots the current state, runs your callback, screenshots the new state, and animates between them. Your callback just changes the DOM as it normally would. That feature-detection guard matters because Firefox only got same-document support in 144.

The gotchas worth knowing before you build on it

Transitions do not run if the navigation is not same-origin. Cross-origin navigations are excluded deliberately for security reasons, so a transition to an external site is never going to work.

The old page's screenshot is taken before the new page renders, which means a slow-loading destination page produces a visible pause with the old page frozen on screen. On a fast site this is invisible. On a site with a 2 second server response, it looks broken. View transitions make slow navigations feel worse, not better, which is an argument for fixing the underlying page load performance first.

Elements with a transition name are lifted out of the normal layout during the animation, which can break things positioned relative to them. Test with real content rather than a placeholder box.

Is it production-ready?

For same-document transitions, yes, with a feature-detection guard. Every current major browser supports it and the fallback is simply that the DOM updates instantly, which is what happens today anyway.

For cross-document, mostly. Chromium and Safari users get the enhancement, Firefox users get an ordinary navigation, and nobody gets a broken page. That is the correct shape for a progressive enhancement and I have shipped it on that basis.

What I would not do is design a critical interaction around it, where the transition carries meaning about what happened. Treat it as polish that some visitors get, because right now that is what it is.

Reference: MDN View Transition API and Chrome for Developers guide.

Previous Post Next Post