CSS View Transitions: native page animations without Framer Motion
The View Transitions API is now a Baseline feature in all major browsers. Here's how to use it in a React SPA and MPA, and exactly where it still falls short.
The View Transitions API landed in Chrome in 2023. Safari 18 and Firefox 144 added support in late 2025. In 2026, it's a Baseline feature - meaning you can ship it without a polyfill to the overwhelming majority of your users, and without pulling in a third-party animation library for the most common cases. Here's how we use it, and where the edges still require Framer Motion or Motion One.
How it works
The API is deceptively simple: wrap a DOM update in `document.startViewTransition()` and the browser automatically captures a before-and-after screenshot, then animates between the two states. The default animation is a crossfade. You customize it entirely through CSS - the browser exposes the old and new states as pseudo-elements (`::view-transition-old` and `::view-transition-new`) that you can animate with standard CSS keyframes.
// SPAs: wrap any state update that should animate
function navigateTo(newRoute: string) {
if (!document.startViewTransition) {
// Fallback for older browsers - just update without animation
setRoute(newRoute);
return;
}
document.startViewTransition(() => {
setRoute(newRoute);
});
}Named transitions: the real power
The default crossfade covers page transitions. The feature that makes View Transitions genuinely compelling is named elements - assigning the same `view-transition-name` to an element in both the before and after state tells the browser to animate that specific element from its old position and size to its new one. This is the 'shared element transition' pattern: a product card in a grid that flies and morphs into a full product page as the user navigates.
/* Assign the same name to the card in the list and the header in the detail view */
.product-card[data-id="42"] {
view-transition-name: product-hero-42;
}
.product-detail-hero {
view-transition-name: product-hero-42;
}
/* The browser interpolates position, size, and border-radius automatically */Cross-document transitions in MPAs
For multi-page apps (no client-side routing), the `@view-transition` CSS at-rule opts the page into cross-document transitions - the browser animates between two full page loads as if they were a SPA navigation. The setup is three lines of CSS. This is the most underused capability in the API: you get smooth page transitions on a plain HTML site with zero JavaScript.
/* Add to both the outgoing and incoming page's stylesheet */
@view-transition {
navigation: auto;
}Where you still need Framer Motion
- Gesture-driven animations (drag-to-dismiss, swipe carousels) - the View Transitions API is declarative and event-driven, not imperative. Framer Motion's layout animations are still better here.
- Spring physics and complex sequencing - CSS keyframes are powerful but verbose for multi-step choreography. Motion One's timeline API handles this with less code.
- Animating elements that are added or removed from the DOM (not just moved) - enter/exit animations for lists and modals still work better with a JS library.
- Safari < 18 and Firefox < 144 - the at-rule for cross-document transitions is ignored by older versions. Plan a graceful no-animation fallback.
Our recommendation
Use View Transitions for navigation-level transitions and shared-element animations between routes - this is exactly what the API was designed for, and the result is noticeably smoother than a JS-driven alternative because the browser handles it at the compositor level. Keep Framer Motion for component-level animations that require interactivity or spring physics. The two coexist cleanly.
Written by Appesto Engineering.