SPA Page Navigation with View Transitions

Animate route changes in React, Vue, or Svelte applications by wrapping your router's setState call in document.startViewTransition().

UI Patterns

Detailed Explanation

The Universal SPA Pattern

Every SPA framework boils down to "set state → re-render → DOM updates". Wrap that final mutation:

// React Router 6.4+
import { unstable_useViewTransitionState } from 'react-router-dom';

function MyLink({ to, children }) {
  return <Link to={to} viewTransition>{children}</Link>;
}
// Manual / Next.js App Router
function navigate(href) {
  if (!document.startViewTransition) {
    router.push(href);
    return;
  }
  document.startViewTransition(() => {
    flushSync(() => router.push(href));
  });
}

The flushSync is critical: by default React batches state updates, but the View Transitions API needs the DOM mutated synchronously inside the callback so it can take the second snapshot at the right moment.

Default cross-fade is enough

For most route transitions, the default cross-fade looks great. You only need custom CSS if you want directional slides (for back vs forward navigation) or shared-element morphs (for hero images in cards).

Per-route customization

Add a CSS class to <html> based on the current route, then scope your transition CSS:

.route-product-detail ::view-transition-new(root) {
  animation: 400ms ease-out both slide-up;
}

This keeps each route's transition isolated and prevents cascading animation rules.

Cross-document alternative (MPA)

For multi-page applications without a JavaScript router, add to both pages' CSS:

@view-transition {
  navigation: auto;
}

The browser handles same-origin navigations automatically.

Use Case

React Router, TanStack Router, Vue Router, SvelteKit, and Next.js App Router applications. Any SPA where you want native-app-quality navigation transitions without bundling Framer Motion or React Spring.

Try ItView Transitions API Generator

Open full tool