How to Implement View Transitions in React Router: A Complete Guide

React Router’s view transitions feature integrates the browser’s native View Transitions API to animate DOM changes during navigation by deferring state updates until the browser finishes the transition animation.

View transitions in React Router provide a seamless way to animate route changes using the platform’s native capabilities. By leveraging the browser’s document.startViewTransition method, the router can interpolate between the outgoing and incoming page states without requiring complex CSS animation libraries. This implementation lives primarily in the router core and DOM adapter layers of the remix-run/react-router repository.

What Are View Transitions in React Router?

View transitions represent a browser-native mechanism for animating changes to the DOM. When enabled in React Router, the router intercepts the final state commit of a navigation and wraps it inside the browser’s view transition lifecycle. This allows the browser to capture the visual state of the outgoing page, capture the state of the incoming page, and execute a morphing animation between the two.

The feature is opt-in on a per-navigation basis. Developers specify viewTransition: true in the navigation options, and the router handles the coordination between React’s state updates and the browser’s animation frame.

How View Transitions Work Under the Hood

The implementation spans three critical phases across packages/react-router/lib/router/router.ts and packages/react-router/lib/components.tsx.

Step 1: Opt-In via the viewTransition Flag

When a navigation is initiated via useNavigate or <Link>, the options object can include the viewTransition boolean. In router.ts, the router stores this flag on the pending navigation entry. This flag persists through loaders, actions, and redirects, ensuring the transition intent survives asynchronous operations.

Step 2: Starting the Browser Transition

When the router is ready to commit the new location—after all loaders have resolved—it checks navigation.state.viewTransition. If the flag is present and the browser supports document.startViewTransition, the router defers the React state update.

In components.tsx, the commit is wrapped as follows:

document.startViewTransition(() => {
  // React state update that swaps the route component
  updateState(newState);
});

This returns a Promise that resolves when the browser’s transition animation completes, allowing React Router to synchronize subsequent operations with the visual lifecycle.

Step 3: Persisting Across Revalidations and Redirects

To handle complex navigation flows, the router serializes the pending view-transition promise into session storage. If a navigation triggers a redirect or a revalidation (such as a POST-redirect-GET pattern), the router retrieves the stored transition state from router.ts and continues the same animation rather than initiating a new one. This ensures seamless visual continuity even when the URL changes multiple times during a single user interaction.

Implementing View Transitions in Your Application

Enabling view transitions requires minimal code changes. You opt-in at the navigation call site and optionally use the useViewTransitionState hook to coordinate CSS.

Enabling Transitions on Navigation

Use the viewTransition option in useNavigate or the <Link> component:

import { useNavigate } from "react-router-dom";

function DashboardLink() {
  const navigate = useNavigate();

  const handleClick = () => {
    navigate("/dashboard", { viewTransition: true });
  };

  return <button onClick={handleClick}>Open Dashboard</button>;
}

For declarative navigation:

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

<Link to="/profile" viewTransition>
  View Profile
</Link>

Styling with useViewTransitionState

The useViewTransitionState hook, exposed from packages/react-router/lib/dom/lib.tsx, provides the transition’s current phase. Use this to apply CSS view transition names or loading states:

import { useViewTransitionState } from "react-router";

function PageWrapper({ children }) {
  const { pending } = useViewTransitionState();

  return (
    <div
      style={{
        viewTransitionName: pending ? "page-transition" : "none",
        opacity: pending ? 0.8 : 1
      }}
    >
      {children}
    </div>
  );
}

Combine this with CSS to define the animation:

::view-transition-old(page-transition) {
  animation: fade-out 0.3s ease;
}

::view-transition-new(page-transition) {
  animation: fade-in 0.3s ease;
}

Key Source Files and Architecture

The view transitions implementation is distributed across the React Router monorepo. Understanding these files helps when debugging transition behavior or contributing to the project.

  • packages/react-router/lib/router/router.ts
    Contains the core logic for storing the viewTransition flag, managing the transition lifecycle through session storage persistence, and coordinating state commits during redirects or revalidations.

  • packages/react-router/lib/components.tsx
    Houses the DOM-specific code that invokes document.startViewTransition and wraps the React state update, bridging the router’s abstract navigation state with the browser’s animation API.

  • packages/react-router/lib/dom/lib.tsx
    Exports the useViewTransitionState hook and TypeScript type definitions for the viewTransition navigation option, providing the public API for components to react to transition states.

  • packages/react-router/lib/context.ts
    Contains documentation comments describing the View Transitions API integration and the intent behind the viewTransition flag, serving as the primary reference for the feature’s design goals.

  • packages/react-router/__tests__/router/view-transition-test.ts
    Comprehensive test suite verifying that view transitions are only applied when opted-in, that they survive loader revalidations, and that they handle redirect chains correctly.

Summary

View transitions in React Router integrate the browser’s native View Transitions API to animate route changes without external animation libraries. Key implementation details include:

  • Opt-in activation via the viewTransition: true flag in navigation options, stored in the router’s pending navigation state in router.ts.
  • Browser coordination through document.startViewTransition in components.tsx, which defers React state commits until the animation completes.
  • Persistence across redirects by serializing transition state to session storage, ensuring seamless animations through revalidations and redirect chains.
  • Component integration via the useViewTransitionState hook in lib.tsx, enabling CSS view transition names and custom loading states.

Frequently Asked Questions

What browsers support view transitions in React Router?

View transitions require the browser to implement the document.startViewTransition API. As of the latest specifications, this is supported in Chromium-based browsers (Chrome, Edge, Opera). React Router gracefully degrades on unsupported browsers by committing state changes immediately without animation, ensuring functionality remains intact across all environments.

You can enable view transitions declaratively by adding the viewTransition prop to the Link component from react-router-dom. When the user clicks the link, React Router automatically sets the internal viewTransition flag to true for that navigation. For programmatic navigation, pass { viewTransition: true } as the second argument to the useNavigate function.

Can view transitions persist through loader revalidations?

Yes, React Router specifically handles this scenario in packages/react-router/lib/router/router.ts. When a navigation triggers a loader revalidation or a redirect, the router serializes the pending view-transition promise into session storage. Upon completing the redirect or revalidation, the router retrieves this state and continues the original transition animation rather than starting a new one, maintaining visual continuity.

How do I style view transitions with CSS?

Use the useViewTransitionState hook to detect when a transition is active, then apply the view-transition-name CSS property to elements you want to animate. The browser creates pseudo-elements (::view-transition-old and ::view-transition-new) representing the outgoing and incoming states. Target these pseudo-elements in your CSS to define custom animations, opacity changes, or transforms during the transition lifecycle.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →