Integrating View Transitions with React Router

Use React Router 6.4's built-in unstable_useViewTransitionState hook to animate route changes — including manual coordination via flushSync for older versions.

Integration

Detailed Explanation

React Router 6.4+ Built-In Support

Since React Router 6.4 (March 2024), every <Link> accepts a viewTransition prop:

import { Link } from 'react-router-dom';

<Link to="/products/42" viewTransition>
  Product 42
</Link>

When the user clicks, React Router internally wraps its navigation in document.startViewTransition(). No further setup needed.

Conditional styling per active transition

To style the transitioning route differently, use the useViewTransitionState hook:

import { useViewTransitionState } from 'react-router-dom';

function ProductCard({ id }) {
  const isTransitioning = useViewTransitionState(`/products/${id}`);
  return (
    <div style={{ viewTransitionName: isTransitioning ? `product-${id}` : 'none' }}>
      …
    </div>
  );
}

This dynamically opts the card in only when it is the source of the current transition, avoiding the unique-name explosion on the entire grid.

Manual integration without the hook (older React Router)

import { flushSync } from 'react-dom';
import { useNavigate } from 'react-router-dom';

function MyLink({ to, children }) {
  const navigate = useNavigate();
  return (
    <a onClick={(e) => {
      e.preventDefault();
      if (!document.startViewTransition) return navigate(to);
      document.startViewTransition(() => {
        flushSync(() => navigate(to));
      });
    }}>{children}</a>
  );
}

The flushSync forces React to commit the navigation synchronously so the browser can take its second snapshot immediately. Without it, React batches the update and the snapshot is taken too early.

Suspense boundaries

If the route uses <Suspense>, the transition will wait for the Suspense boundary to resolve before snapshotting. Render a meaningful skeleton inside Suspense so the cross-fade animates between two real-looking views, not between content and a spinner.

Use Case

Production React apps using React Router 6.4+ for navigation. Especially powerful for product detail pages, photo gallery deep-links, and any 'list → detail → list' flow where the same item appears in different layouts.

Try ItView Transitions API Generator

Open full tool