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:
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:
Fade
Slide
Scale
Cross-fade
Shared-element movement
Directional navigation
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 browser does not support the required API
The user disables animation
JavaScript behavior changes
A transition cannot be created
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:
Large images
Complex shadows
Expensive filters
Huge DOM trees
Multiple simultaneous transitions
Long animation durations
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:
Product images
Article thumbnails
Profile photos
Selected navigation items
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.

Join the conversation! Your thoughts help the community grow.