DEV Community

Vivian Voss
Vivian Voss

Posted on • Originally published at vivianvoss.net

The Page Transition You Never Had to Build

Split scene: oversized industrial tools (jackhammer, crane, welding gear, chains) struggle to connect two small pages, while the Developer holds a tiny paintbrush with three elegant glowing strokes already bridging the gap. Three lines of CSS replace entire frameworks.

Stack Patterns — Episode 11

"We need a single-page application because users expect smooth transitions between pages."

One has heard this argument rather a lot over the past decade. It justified React. It justified Vue Router. It justified Framer Motion (32KB minified). It justified Barba.js. It justified entire application architectures built around the premise that clicking a link should not, under any circumstances, feel like clicking a link.

400KB of JavaScript so that a heading could fade whilst the URL changed. Marvellous.

The browser does it now. In CSS. Three lines.

The Pattern

Both pages include this CSS:

@view-transition {
  navigation: auto;
}
Enter fullscreen mode Exit fullscreen mode

That is the entire opt-in. Click a link that navigates to another page on the same origin. The browser takes a screenshot of the old page, loads the new page, takes a screenshot of the new page, and cross-fades between them. No JavaScript. No framework. No hydration. No virtual DOM diffing the entire document tree so that a title can slide thirty pixels to the left.

The default transition is a cross-fade. It works immediately. For many sites, this is sufficient, and one does find sufficiency rather underrated.

Named Transitions

Want a specific element to animate from its old position to its new one? One CSS property:

h1 { view-transition-name: heading; }
Enter fullscreen mode Exit fullscreen mode

The same view-transition-name on the corresponding element on both pages. The browser snapshots the old position, snapshots the new position, and animates between them. The element does not need to be the same DOM node. It does not need to be the same component. It does not need to exist in the same JavaScript context. It merely needs the same name.

This is the key insight: the browser matches elements across pages by name, not by identity. Two entirely separate HTML documents, served by any backend in any language, connected only by a CSS property. The elegance is rather difficult to overstate.

You can name as many elements as you wish:

h1       { view-transition-name: heading; }
.hero    { view-transition-name: hero; }
nav      { view-transition-name: navigation; }
.sidebar { view-transition-name: sidebar; }
Enter fullscreen mode Exit fullscreen mode

Each named element transitions independently. Unnamed elements participate in the default cross-fade.

Custom Animations

The API exposes CSS pseudo-elements for both the old and new states:

::view-transition-old(heading) {
  animation: 0.3s ease-out slide-out;
}
::view-transition-new(heading) {
  animation: 0.3s ease-in slide-in;
}
Enter fullscreen mode Exit fullscreen mode

The old state and the new state are separate snapshots rendered as replaced elements. You animate them independently. The browser composites them. The result is buttery smooth because it happens in the compositor thread, not in JavaScript.

You can slide, scale, rotate, clip, or apply any CSS animation you would normally use. The full power of CSS animations and keyframes is available. No library API to learn. No React hooks to chain. Just CSS.

The Cost We Paid

Let us be honest about what the industry traded for smooth page transitions.

We abandoned server-rendered HTML. We shipped 300KB JavaScript runtimes to the client. We broke the back button and spent engineering hours rebuilding it in JavaScript. We reinvented routing in userland because the browser's native navigation was "not smooth enough." We lost native browser caching. We invented hydration to fix the problem we created by removing HTML in the first place. We built entire state management libraries (Redux, Zustand, Jotai, Pinia, the list grows quarterly) so the client could remember what the server already knew.

We broke accessibility. Screen readers that worked perfectly with server-rendered pages now had to contend with JavaScript-mutated DOMs, focus management nightmares, and route changes that announced nothing. We broke SEO. Google had to build a headless Chrome renderer just to index SPA content, and for years, sites that relied on client-side rendering ranked lower simply because the crawler could not see the content.

We broke the browser's loading indicator. Users no longer knew whether a page was loading or frozen. So we built skeleton screens to simulate the loading indicator we had removed. Marvellous.

All so a heading could slide.

What It Replaces

The View Transition API does not replace everything. Framer Motion and GSAP remain valuable for complex interactive animations: drag-and-drop, spring physics, gesture-driven sequences. Those are runtime interactions, not page transitions. The distinction matters.

But for page-to-page navigation transitions? The use case that justified SPAs for millions of websites that are, fundamentally, collections of pages linked together with anchor tags?

Solution Size JavaScript Required Works Without JS
Barba.js 7.5KB min Yes No
Framer Motion 32KB min Yes (React) No
SPA router Entire framework Yes No
View Transition API 0KB No Graceful fallback

Zero kilobytes. Because it is the browser. The runtime you already shipped.

Browser Support

Here is where honesty matters, and where many articles about this API get rather conveniently vague.

Full cross-document support:

  • Chrome 126+ (desktop and Android)
  • Edge 126+
  • Safari 18.2+ (including iOS Safari)
  • Opera 112+
  • Samsung Internet 29+

Partial or no cross-document support:

  • Firefox 146+: supports same-document view transitions (Level 1), but cross-document transitions remain behind a flag (dom.viewTransitions.enabled in Nightly only as of April 2026)
  • Opera Mini, KaiOS Browser, UC Browser: no support

Global coverage: 87.82% of users (CanIUse, April 2026). That is high, but it is not universal. Firefox's absence from the cross-document specification is notable, and for teams whose audience includes a significant Firefox share, this is a legitimate consideration.

Why it does not matter as much as you think:

For browsers that do not support the API: nothing breaks. The user sees a normal page load. No error. No fallback code. No polyfill. No broken layout. No JavaScript exception. The page loads exactly as it would have loaded before the API existed.

This is progressive enhancement in its purest form. The enhanced experience is literally free for supporting browsers (no additional bytes shipped), and the baseline experience is what every user had before. You are not degrading the experience for unsupported browsers. You are enhancing it for supported ones. The risk is zero. The cost is three lines of CSS.

One does rather appreciate an API that fails by doing nothing wrong.

Compare this with the SPA approach: if your JavaScript bundle fails to load (network timeout, CDN outage, ad blocker, corporate proxy), your users see a blank white page. The "enhanced" experience degrades to nothing. The View Transition API degrades to a normal page load. One does note the asymmetry.

The Constraints

Same-origin only. Cross-document view transitions work for same-origin navigations only. You cannot animate a transition from your site to an external URL. This is a security constraint, not a limitation: the browser needs access to both documents to snapshot them. If your site links to external domains, those navigations simply behave normally.

Unique names per page. Each view-transition-name must be unique within a single document. Two elements on the same page cannot share a name. This is rarely a problem in practice, but it means you cannot, for example, animate all list items with the same name. Each needs its own.

Performance consideration. The browser captures raster screenshots of named elements. Naming dozens of elements on a complex page could impact transition performance. In practice, naming three to five key elements (header, hero image, navigation, main content area) produces smooth results. Naming everything is rather missing the point of selective enhancement.

No cross-origin. Worth stating twice. If your architecture relies on navigating between subdomains (app.example.com to docs.example.com), these are cross-origin navigations and will not trigger view transitions. The same-origin policy applies strictly.

Same-Document Transitions

For single-page applications, the same-document View Transition API (available since Chrome 111, Safari 18+, Firefox 133+) provides equivalent functionality using document.startViewTransition(). This variant has broader browser support because it landed earlier.

But the cross-document variant is the interesting one, because it means multi-page applications, the architecture the web was built for, can now match the perceived smoothness of SPAs. Without being SPAs. Without the JavaScript. Without the complexity. Without breaking the back button, the cache, the loading indicator, accessibility, SEO, or the fundamental contract between browser and server.

The Point

For a decade, "smooth page transitions" was the argument that justified client-side routing, framework adoption, and the entire SPA architecture for websites that were, at their core, pages linked together with anchor tags. The browser lacked the capability, so we built it in JavaScript. Reasonably enough.

The browser has the capability now. Three lines of CSS. Zero JavaScript. Progressive enhancement built in. 87.82% global support and climbing. Firefox is the last holdout for cross-document, and the same-document variant already works there.

The argument no longer applies. The question is no longer "should we use an SPA for transitions?" The question is: "what were the other reasons?" One does suspect the list is rather shorter than expected.

One does wonder how many SPAs will be reconsidered. One does rather suspect the answer is: not enough.

Read the full article on vivianvoss.net →


By Vivian Voss — System Architect & Software Developer. Follow me on LinkedIn for daily technical writing.

Top comments (0)