Modern web applications increasingly use application-style navigation instead of traditional full-page reloads. React applications can change routes, replace page content, and preserve parts of the interface without rebuilding the entire document.

The challenge is making those transitions feel natural.

Without an animation layer, navigation can appear abrupt:

Page A
   |
   | Navigate
   v
Page B

With the View Transition API, the browser can capture the old and new visual states and animate between them.

React 19.3 adds a built-in ViewTransition component and related transition support, allowing developers to coordinate visual transitions with React rendering and navigation. The important point is that this does not require replacing an existing router.

React can work with router navigation while providing a declarative place to describe transition behavior.

This article explains how React 19.3 View Transitions work, how to use them with client-side routing, how to create page and element transitions, and how to avoid common implementation problems.

What Is the View Transition API?

The View Transition API allows a browser to animate changes between two visual states of a document.

Without a transition:

Current UI
    |
    v
DOM Update
    |
    v
New UI

With a view transition:

Current UI
    |
    v
Capture Old State
    |
    v
DOM Update
    |
    v
Capture New State
    |
    v
Animate

The browser handles much of the visual interpolation.

This is different from manually implementing animations with:

React's integration provides a declarative mechanism around this browser capability.

What React 19.3 Adds

React 19.3 introduces the ViewTransition component for coordinating visual transitions with React updates.

A simplified example is:

import { ViewTransition } from "react";

function Page() {
  return (
    <ViewTransition>
      <main>
        <h1>Dashboard</h1>
      </main>
    </ViewTransition>
  );
}

The component identifies content that React should coordinate with a view transition.

The transition can then be customized using CSS.

For example:

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 250ms;
}

The exact transition structure depends on what elements participate in the transition.

Why React Needs View Transition Support

React already manages UI changes efficiently.

However, efficient rendering does not automatically produce visually smooth navigation.

Consider:

function App() {
  const [page, setPage] = useState("home");

  return (
    <>
      {page === "home" ? <Home /> : <About />}
    </>
  );
}

When page changes, React updates the UI.

The browser sees:

<Home />

becoming:

<About />

Without a view transition, the visual change can be immediate.

React's View Transition support lets the rendering lifecycle coordinate with the browser's transition mechanism.

Basic ViewTransition Example

A simple component can wrap a section of the interface:

import { ViewTransition } from "react";

export default function Dashboard() {
  return (
    <ViewTransition>
      <section className="dashboard">
        <h1>Dashboard</h1>
        <p>Welcome to your dashboard.</p>
      </section>
    </ViewTransition>
  );
}

You can then define transition behavior with CSS:

.dashboard {
  view-transition-name: dashboard;
}

This gives the browser a stable identity for the element participating in the transition.

View Transition Names

view-transition-name is an important concept when creating element-level transitions.

Suppose a list contains:

<div className="product">
  <img src="/images/laptop.png" alt="Laptop" />
  <h2>Laptop</h2>
</div>

You can assign a transition name:

.product {
  view-transition-name: product;
}

The browser can then associate the corresponding visual element between states.

Conceptually:

Page A                         Page B

Product Card                   Product Details
     |                               |
     +-------- same identity --------+
                  |
                  v
             View Transition

This is especially useful for interfaces where an element appears in one view and becomes a larger or differently positioned element in another.

Using View Transitions With a Router

A common misconception is that adopting React View Transitions requires replacing React Router or another existing routing solution.

It does not.

The router remains responsible for navigation:

User Click
   |
   v
Router
   |
   v
Route Change
   |
   v
React Rendering
   |
   v
View Transition

This separation is important.

The router answers:

Which route should be displayed?

React answers:

Which components should render?

The View Transition system answers:

How should the visual change between states be animated?

These responsibilities can coexist.

Example With React Router

A simplified routing application might look like:

import {
  BrowserRouter,
  Routes,
  Route
} from "react-router-dom";

import Home from "./Home";
import Products from "./Products";
import ProductDetails from "./ProductDetails";

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/products" element={<Products />} />
        <Route
          path="/products/:id"
          element={<ProductDetails />}
        />
      </Routes>
    </BrowserRouter>
  );
}

The router does not need to be replaced.

Instead, transition-aware components can be introduced around the portions of the UI that should animate.

Creating a Page Transition

A common requirement is to animate the entire page when the route changes.

For example:

import { ViewTransition } from "react";

function PageLayout({ children }) {
  return (
    <ViewTransition>
      <main className="page">
        {children}
      </main>
    </ViewTransition>
  );
}

The routing layer can continue deciding which page to render.

The transition layer handles the visual change.

A simple fade can be implemented with CSS:

::view-transition-old(root) {
  animation: fade-out 150ms ease;
}

::view-transition-new(root) {
  animation: fade-in 200ms ease;
}

@keyframes fade-out {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

This keeps the animation declarative.

Creating a Shared Element Transition

Page-level transitions are useful, but shared-element transitions can make applications feel significantly more polished.

Consider a product list:

Products
+------------------+
| Laptop           |
| Image            |
+------------------+

When the user clicks the laptop:

Product Details
+------------------------------+
|                              |
|        Large Laptop Image    |
|                              |
+------------------------------+

A shared transition can visually connect the two states.

Assign a stable transition name:

<img
  src={product.image}
  alt={product.name}
  style={{
    viewTransitionName: `product-${product.id}`
  }}
/>

The same product can use the same transition name on the details page.

The browser can then animate the visual transformation between the two states.

Avoid Duplicate Transition Names

A transition name should identify a unique visual element within the active transition.

This can cause problems:

products.map(product => (
  <div
    key={product.id}
    style={{ viewTransitionName: "product" }}
  >
    {product.name}
  </div>
))

Multiple elements use the same name.

Instead, make the identity specific:

products.map(product => (
  <div
    key={product.id}
    style={{
      viewTransitionName: `product-${product.id}`
    }}
  >
    {product.name}
  </div>
))

This is especially important for lists.

React Keys and View Transition Names Are Different

Developers sometimes confuse React's key with view-transition-name.

React's key:

<div key={product.id}>

helps React identify elements between renders.

A view transition name:

view-transition-name: product;

helps the browser identify visual elements for transition purposes.

They solve different problems.

You may need both:

<div
  key={product.id}
  style={{
    viewTransitionName: `product-${product.id}`
  }}
>
  ...
</div>

Navigation Without Replacing the Router

Suppose your application already uses React Router.

You do not need to rewrite:

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

The navigation mechanism can remain the same.

The transition behavior is an additional layer.

Conceptually:

<Link>
   |
   v
React Router
   |
   v
Route Update
   |
   v
React View Transition
   |
   v
Animated UI

This is one of the biggest practical benefits of the React integration.

Teams can adopt transitions incrementally.

Transitioning Navigation With State Updates

View transitions can also apply to state changes that are not route changes.

For example:

import { useState, ViewTransition } from "react";

export default function Tabs() {
  const [activeTab, setActiveTab] = useState("overview");

  return (
    <div>
      <button onClick={() => setActiveTab("overview")}>
        Overview
      </button>

      <button onClick={() => setActiveTab("activity")}>
        Activity
      </button>

      <ViewTransition>
        {activeTab === "overview"
          ? <Overview />
          : <Activity />}
      </ViewTransition>
    </div>
  );
}

The transition mechanism is therefore not limited to URLs.

It can coordinate visual changes caused by React state.

Coordinating Suspense and Navigation

Real applications often load data asynchronously.

For example:

Navigation
   |
   v
Route
   |
   v
Data Request
   |
   v
Suspense
   |
   v
Content

A transition should not make the application feel stuck while data loads.

React's transition model can coordinate rendering work with asynchronous UI states.

A practical interface might provide:

Old Page
   |
   v
Navigation
   |
   v
Loading State
   |
   v
New Page

The exact behavior depends on how the router, Suspense boundaries, and data-loading architecture are implemented.

The important principle is to avoid animating a large blank region simply because the new route's data has not arrived.

CSS Controls the Visual Result

React coordinates the transition, but CSS controls its appearance.

For example:

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 200ms;
  animation-timing-function: ease-in-out;
}

You can create:

For a subtle page transition:

::view-transition-old(root) {
  animation: page-out 180ms ease;
}

::view-transition-new(root) {
  animation: page-in 220ms ease;
}

@keyframes page-out {
  to {
    opacity: 0;
  }
}

@keyframes page-in {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

Keep navigation animations short.

A transition that takes 800ms can make a fast application feel slower.

Respecting Reduced Motion

Accessibility should be considered from the beginning.

Users who enable reduced-motion preferences may not want elaborate page animations.

Use:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 1ms;
  }
}

This preserves the transition mechanism while effectively minimizing the animation.

For more complex applications, you can define a separate reduced-motion strategy.

Progressive Enhancement

View Transitions should be treated as an enhancement rather than a requirement for basic navigation.

The application should still work when:

The correct hierarchy is:

Application Functionality
        ↓
Routing
        ↓
Rendering
        ↓
Optional Animation

Animation should never become the foundation of navigation.

Browser Support Considerations

The View Transition API is a browser capability.

That means developers should consider support for the browsers used by their actual audience.

A good strategy is:

Supported Browser
    ↓
View Transition
    ↓
Animated Navigation

Unsupported Browser
    ↓
Normal Navigation

The application should not fail merely because the browser does not support view transitions.

Common Mistake: Animating Everything

A common implementation mistake is assigning transition names to every element.

For example:

Header
Logo
Navigation
Sidebar
Card
Button
Text
Image
Footer

If all of these participate in every transition, the visual result can become distracting and expensive to reason about.

Instead, identify the elements that communicate continuity.

For example:

Product Card
     ↓
Product Details

The product image and title may benefit from a shared transition.

The footer probably does not.

Common Mistake: Using Unstable Transition Names

This is problematic:

style={{
  viewTransitionName: `item-${Math.random()}`
}}

The identity changes on every render.

A transition needs stable identity across the states being animated.

Use a deterministic identifier:

style={{
  viewTransitionName: `item-${item.id}`
}}

Common Mistake: Ignoring Layout Changes

A shared-element transition is most effective when the application provides a clear visual relationship between the old and new states.

If an element changes:

Position
Size
Visibility
Structure

all at once, the resulting animation may not communicate the intended relationship.

Design the transition around a meaningful visual identity rather than simply adding view-transition-name everywhere.

Common Mistake: Long Animations

Avoid:

animation-duration: 1s;

for normal navigation unless the interaction specifically requires it.

For most application navigation, shorter transitions feel more responsive.

A useful starting point is around:

150ms–300ms

Then evaluate the actual user experience.

Common Mistake: Forgetting Focus Management

Visual transitions do not replace accessibility behavior.

When navigating between routes, keyboard focus should still move appropriately.

For example, after navigation:

Route Change
   |
   +--> Visual Transition
   |
   +--> Focus Management
   |
   +--> Screen Reader Context

These concerns are separate.

An attractive transition is not a substitute for accessible navigation.

Debugging View Transitions

When a transition does not work, inspect the problem systematically.

Step 1: Confirm the Element Exists

Make sure the component containing ViewTransition actually renders.

Step 2: Check CSS

Inspect:

view-transition-name

and:

::view-transition-old(...)
::view-transition-new(...)

Step 3: Check Duplicate Names

Make sure multiple elements are not unintentionally assigned the same transition name.

Step 4: Check Browser Support

Confirm that the browser supports the View Transition API required by the implementation.

Step 5: Remove Custom CSS

Test with a minimal transition first.

If the default behavior works, reintroduce custom animation rules gradually.

Step 6: Test Reduced Motion

Your CSS should behave correctly when:

prefers-reduced-motion: reduce

is enabled.

View Transitions and Server Rendering

React applications may use server-side rendering, static generation, or client-side rendering.

View transitions do not eliminate the distinction between these rendering strategies.

For example:

Server Rendering
       ↓
Initial HTML
       ↓
Hydration
       ↓
Client Navigation
       ↓
View Transition

The transition is primarily relevant to changes between visual states.

It should therefore be designed with the application's rendering architecture in mind.

For server-rendered applications, developers should also consider whether the transition should occur during initial page loading or only during subsequent navigation.

View Transitions vs. Traditional CSS Animations

Feature

Traditional CSS Animation

View Transitions

Primary target

Existing elements

Changes between UI states

DOM state awareness

Developer-managed

Browser-managed snapshots

Shared elements

More manual

Built into transition model

Route transitions

Requires integration

Designed for state/document changes

Implementation

CSS/classes/JS

React + CSS + browser API

Progressive enhancement

Straightforward

Must account for browser support

Best use case

Component-level motion

State/page visual transitions

Traditional CSS animations are still useful.

View Transitions do not replace them.

A modern application may use both:

View Transitions
       +
CSS Animations
       +
CSS Transitions

Each solves a different problem.

Building a Practical Product Navigation

Consider an e-commerce application.

The product list:

function ProductCard({ product }) {
  return (
    <article>
      <img
        src={product.image}
        alt={product.name}
        style={{
          viewTransitionName: `product-image-${product.id}`
        }}
      />

      <h2>{product.name}</h2>

      <Link to={`/products/${product.id}`}>
        View details
      </Link>
    </article>
  );
}

The product details page can use the same identity:

function ProductDetails({ product }) {
  return (
    <article>
      <img
        src={product.image}
        alt={product.name}
        style={{
          viewTransitionName: `product-image-${product.id}`
        }}
      />

      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </article>
  );
}

The router remains responsible for:

/products

and:

/products/:id

while the browser receives a stable visual identity for the product image.

This is the kind of transition that can make navigation feel connected rather than like two unrelated screens.

Performance Considerations

Animations should not introduce unnecessary rendering work.

Keep an eye on:

A transition can look smooth on a desktop workstation but perform poorly on a low-end mobile device.

Test on realistic hardware.

The goal is:

Visual continuity
+
Responsive interaction

not:

Maximum animation complexity

Advantages

Better Navigation Experience

Transitions make route changes feel more continuous.

Less Manual Animation Code

The browser handles visual snapshots instead of developers manually cloning and coordinating elements.

Shared-Element Effects

Elements can visually connect across application states.

Works With Existing Routing

You can add transition behavior without replacing your routing architecture.

Declarative React Integration

The ViewTransition component provides a React-oriented way to participate in transitions.

Progressive Adoption

Teams can start with a simple page fade and gradually introduce shared-element transitions.

Disadvantages and Limitations

Browser Support

The underlying API depends on browser support.

More CSS Complexity

Advanced transitions require understanding pseudo-elements and transition naming.

Debugging Can Be Less Intuitive

The browser is animating snapshots rather than simply changing an element's CSS properties.

Accessibility Still Requires Separate Work

Focus management and reduced-motion behavior must be considered independently.

Poorly Designed Transitions Can Hurt UX

Animation is not automatically an improvement.

Large Transitions Can Affect Performance

Complex visual changes can be expensive on constrained devices.

Recommended Implementation Strategy

A production migration can be incremental.

Phase 1: Add a Simple Page Transition

Start with a short fade.

Route A
  ↓
Fade
  ↓
Route B

Phase 2: Identify Important Shared Elements

Choose elements such as:

Phase 3: Add Stable Transition Names

Use deterministic IDs.

Phase 4: Add Reduced Motion

Respect user preferences.

Phase 5: Test on Mobile

Validate performance on real devices.

Phase 6: Refine

Only add more complex animation where it improves comprehension or continuity.

Production Checklist

Before shipping View Transitions, verify:

[ ] Existing router continues to handle navigation
[ ] ViewTransition is used only where useful
[ ] Transition names are stable
[ ] Duplicate transition names are avoided
[ ] Unsupported browsers still navigate correctly
[ ] Reduced motion is respected
[ ] Keyboard focus remains correct
[ ] Screen reader behavior is unaffected
[ ] Animations are short and purposeful
[ ] Large images are optimized
[ ] Mobile performance has been tested
[ ] Route loading states are handled correctly

Conclusion

React 19.3's View Transition support provides a more integrated way to add visual continuity to React applications.

The key architectural idea is simple:

Router
  |
  +--> Decides where to navigate

React
  |
  +--> Decides what to render

View Transition
  |
  +--> Decides how the visual change is presented

This means teams do not need to throw away an existing router or rebuild navigation just to add page animations.

Start with simple transitions, use stable identities for shared elements, keep animations short, and treat motion as progressive enhancement. Most importantly, preserve accessibility and application functionality independently of the animation layer.

When used selectively, View Transitions can turn abrupt route changes into a much more coherent experience without turning the application's navigation architecture upside down.