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

> Learn how to implement smooth view transitions in React Router using the native View Transitions API. Animate DOM changes seamlessly for a better user experience.

- Repository: [Remix/react-router](https://github.com/remix-run/react-router)
- Tags: how-to-guide
- Published: 2026-03-06

---

**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`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/router/router.ts) and [`packages/react-router/lib/components.tsx`](https://github.com/remix-run/react-router/blob/main/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`](https://github.com/remix-run/react-router/blob/main/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`](https://github.com/remix-run/react-router/blob/main/components.tsx), the commit is wrapped as follows:

```javascript
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`](https://github.com/remix-run/react-router/blob/main/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:

```tsx
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:

```tsx
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`](https://github.com/remix-run/react-router/blob/main/packages/react-router/lib/dom/lib.tsx), provides the transition’s current phase. Use this to apply CSS view transition names or loading states:

```tsx
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:

```css
::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`](https://github.com/remix-run/react-router/blob/main/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`](https://github.com/remix-run/react-router/blob/main/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`](https://github.com/remix-run/react-router/blob/main/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`](https://github.com/remix-run/react-router/blob/main/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`](https://github.com/remix-run/react-router/blob/main/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`](https://github.com/remix-run/react-router/blob/main/router.ts).
- **Browser coordination** through `document.startViewTransition` in [`components.tsx`](https://github.com/remix-run/react-router/blob/main/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`](https://github.com/remix-run/react-router/blob/main/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.

### How do I enable view transitions for Link components?

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`](https://github.com/remix-run/react-router/blob/main/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.