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:
CSS classes
JavaScript timers
Element cloning
Opacity manipulation
Position calculations
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:
Join the conversation! Your thoughts help the community grow.