View Transitions
Animate between states (same-document) or between pages (cross-document) with a single CSS opt-in. Replaces ad-hoc SPA animation libraries with a platform primitive.
What it is
The View Transitions API animates the change from one DOM state to another — or from one document to the next — by taking a snapshot of the old state, applying the change, taking a snapshot of the new state, and cross-fading between them. There are two flavours.
Same-document (JS-driven). A single-page-app pattern. Wrap any DOM mutation in document.startViewTransition():
document.startViewTransition(() => {
// any DOM update — render new view, swap routes, etc.
app.navigate(targetUrl);
});
Cross-document (declarative). Two normal HTML pages opt into a shared transition with a single CSS rule on each:
@view-transition {
navigation: auto;
}
When the user navigates from page A to page B and both pages have this rule, the browser captures A, replaces it with B, and cross-fades. No JavaScript required.
This site ships it. global.css carries @view-transition { navigation: auto }, so cross-document navigations between any two pages on specification.website cross-fade on supporting browsers. The matching prefers-reduced-motion: reduce rule zeroes animation-duration on the view-transition pseudo-elements, so reduced-motion users get an instant swap. Pairs with our Speculation Rules prerender — the next page is already in memory, then it animates in.
You shape the animation with the ::view-transition-* pseudo-elements:
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 200ms;
}
/* Tag elements that should animate independently */
.hero {
view-transition-name: hero;
}
Why it matters
- Replaces a stack of dependencies. Framer Motion, GSAP page transitions, hand-rolled FLIP — replaced by a CSS declaration and a pseudo-element.
- Bridges MPA and SPA UX. Cross-document view transitions give a traditional multi-page site the feel of an SPA without an SPA’s complexity, JavaScript weight, or accessibility regressions.
- Pairs with Speculation Rules. A prerendered next page + a cross-document view transition is the closest the platform gets to “instant, animated” navigation.
- Respects user preferences. Browsers honour
prefers-reduced-motion: reduceand skip the animation. You should still check it explicitly when you customise.
How to implement
Cross-document, the minimum:
@view-transition {
navigation: auto;
}
Ship that on every page you want to participate. The browser handles the rest.
Same-document, the minimum:
if (!document.startViewTransition) {
updateDom();
return;
}
document.startViewTransition(() => updateDom());
Feature-detect — older browsers run the callback synchronously without the transition.
Tag persistent elements so they morph instead of cross-fading:
.product-card[data-id="42"] { view-transition-name: product-42; }
The same name on both old and new DOM signals “this is the same conceptual element — animate the position/size delta.”
Respect reduced motion:
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
Keep transitions short — 150–250ms. Longer feels sluggish; shorter feels like a flash.
Common mistakes
- Same
view-transition-nameon multiple elements at once. The browser logs an error and skips the morph. - Animating layout-dependent properties (width, height) when you could animate transforms. Transforms compositor-only, layout properties trigger reflow.
- Long animations on data-heavy navigations. The user is waiting for the page, not for your animation. Cap at ~250ms.
- Ignoring
prefers-reduced-motion. People with vestibular disorders should not be motion-tested for a route change. - Same-document transitions that swallow exceptions in the callback. Wrap with try/catch and re-throw on transition rejection.
Verification
- DevTools → Animations panel. Records every view transition with timing.
caniuse.com/view-transitions— current browser support matrix. Same-document is broadly available; cross-document still rolling out (Chromium first, WebKit shipping, Gecko in progress).- Toggle
prefers-reduced-motion: reducein DevTools → Rendering. The transition must disappear, not just shorten. - Run a Lighthouse audit on the destination page — adding view transitions must not regress LCP or INP. If it does, your animation is too long or too expensive.