Native Cross-Document View Transitions

The modern View Transitions API: smooth page transitions for a multi-page site with a single line of CSS.

Native Cross-Document View Transitions

Table of Contents

The Modern API

The View Transitions API now supports cross-document navigation — the same smooth transition engine that powers single-page apps, built directly into the browser for ordinary multi-page sites. No JavaScript, no router, no framework integration. You opt in with a single CSS rule, and the browser handles the rest.

The Outdated Syntax Still Floating Around

Most tutorials about view transitions you’ll come across are already out of date. They generally fall into one of two camps:

  1. SPA-style routers. On Astro, that’s the <ClientRouter /> component (formerly <ViewTransitions />) with transition:animate and transition:persist directives scattered through layouts. It works, but it turns a multi-page site into a client-side-routed one — JavaScript owns navigation instead of the browser, and you inherit SPA concerns like scroll restoration and scripts re-running on every route.

  2. document.startViewTransition(). That API is for same-document transitions, i.e. transitions you drive yourself from JavaScript. It’s still around, but it’s the wrong tool when you have real page navigations — and it’s no longer necessary now that the browser transitions between documents natively.

Both approaches predate native cross-document support. A good rule of thumb: if a view transitions article reaches for a router or a JavaScript API to animate a page change, it was probably written before the modern API landed.

The One-Line Setup

@view-transition {
  navigation: auto;
}

That’s it. Every same-origin navigation now cross-fades between the outgoing and incoming page. The outgoing page stays frozen on screen while the new page’s stylesheets finish loading, so there’s no flash of unstyled content (FOUT) — the swap happens once, smoothly, when the new page is ready.

Tuning the Crossfade

The default crossfade is perfectly usable. If you want it tuned, you can override the snapshots with your own animations:

::view-transition-old(root) {
  animation: 200ms ease-in both view-fade-out;
  mix-blend-mode: normal;
}

::view-transition-new(root) {
  animation: 200ms ease-out both view-fade-in;
  mix-blend-mode: normal;
}

@keyframes view-fade-out { to { opacity: 0; } }
@keyframes view-fade-in { from { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

The mix-blend-mode: normal avoids the brightening the browser’s default blending can cause when pages have very different backgrounds.

Morphing an Element Across Pages

The nicest touch: give two elements the same view-transition-name and the browser will morph one into the other across the navigation. On this blog, each card thumbnail and its article banner share a name derived from the post slug:

<!-- blog card thumbnail -->
<img style="view-transition-name: banner-my-post" src="..." />

<!-- article banner -->
<img style="view-transition-name: banner-my-post" src="..." />

Clicking a card makes the thumbnail smoothly scale into the article hero, so the image appears to survive the page change rather than being replaced.

Browser Support

Cross-document view transitions are supported in Chromium-based browsers (Chrome, Edge). Firefox and Safari ignore @view-transition and fall back to a normal full page load — exactly the behavior the site had before, so there’s nothing to polyfill. Combined with the reduced-motion handling above, it’s a purely additive enhancement: better where supported, unchanged where it isn’t.

Related Articles

Latest Articles

Go to top of page