Modern web applications are expected to feel responsive, not just functional. A page that changes instantly can work perfectly, but a well-designed transition can make navigation, expanding content, and changing components feel much more natural.
React has traditionally left most UI animation work to CSS animations, CSS transitions, or third-party animation libraries. React 19.3 changes the picture with the stable <ViewTransition> component, which integrates React's rendering model with the browser View Transition API.
The important part is that React does not simply provide another animation wrapper. It connects View Transitions with React Transitions, Suspense, deferred updates, and component lifecycle behavior.
This article explains how React 19.3 View Transitions work, how to implement them, how to customize animations, and what to consider before using them in a production application.
What Are View Transitions in React?
A View Transition allows the browser to visually animate a change between two UI states.
For example, consider a product listing:
A user clicks a product.
The application renders the product details.
Instead of replacing the old UI immediately, the browser creates a visual transition between the previous and next states.
React 19.3 provides the <ViewTransition> component for this purpose.
A basic example looks like this:
import { ViewTransition } from "react";
function ProductCard({ product }) {
return (
<ViewTransition>
<div className="product-card">
<h2>{product.name}</h2>
<p>{product.description}</p>
</div>
</ViewTransition>
);
}
React coordinates the browser's View Transition API behind the scenes. You generally do not need to call document.startViewTransition() yourself when using React's built-in implementation.
React 19.3 makes View Transitions stable after they were previously available as an experimental API.
How React View Transitions Work
The key concept is that <ViewTransition> describes what part of the UI should participate in an animation, while a React Transition determines when the update should be animated.
Consider this example:
import { ViewTransition, startTransition, useState } from "react";
export default function App() {
const [showDetails, setShowDetails] = useState(false);
function toggleDetails() {
startTransition(() => {
setShowDetails(value => !value);
});
}
return (
<div>
<button onClick={toggleDetails}>
{showDetails ? "Hide details" : "Show details"}
</button>
{showDetails && (
<ViewTransition>
<section className="details">
<h2>Product Details</h2>
<p>This section is displayed using a View Transition.</p>
</section>
</ViewTransition>
)}
</div>
);
}
The important detail is startTransition().
A normal state update is considered urgent:
setShowDetails(true);
A state update wrapped in startTransition() is treated as a non-urgent Transition:
startTransition(() => {
setShowDetails(true);
});
React can then use that Transition to activate the corresponding <ViewTransition>.
React documents three common transition situations:
Enter — a View Transition is added.
Exit — a View Transition is removed.
Update — content, style, position, or size changes.
Share — a named View Transition moves from one part of the UI to another.
Building an Enter and Exit Animation
A practical use case is displaying and hiding a panel.
import { ViewTransition, startTransition, useState } from "react";
export default function SettingsPanel() {
const [visible, setVisible] = useState(false);
const togglePanel = () => {
startTransition(() => {
setVisible(current => !current);
});
};
return (
<div>
<button onClick={togglePanel}>
{visible ? "Close Settings" : "Open Settings"}
</button>
{visible && (
<ViewTransition
enter="panel-enter"
exit="panel-exit"
default="none"
>
<div className="settings-panel">
<h2>Settings</h2>
<p>Manage your application preferences.</p>
</div>
</ViewTransition>
)}
</div>
);
}
Here, enter and exit specify CSS class names that React can use for the respective View Transition types.
The default="none" setting is useful when you want to explicitly control which transition types are animated instead of allowing the default animation to apply everywhere.
Customizing View Transition Animations with CSS
The default View Transition behavior is a cross-fade. For a production application, you will often want an animation that matches the application's design.
For example:
::view-transition-new(.panel-enter) {
animation: slide-in 250ms ease-out;
}
::view-transition-old(.panel-exit) {
animation: slide-out 200ms ease-in;
}
@keyframes slide-in {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slide-out {
from {
opacity: 1;
transform: translateY(0);
}
to {
opacity: 0;
transform: translateY(-8px);
}
}
The React component remains focused on application behavior:
<ViewTransition
enter="panel-enter"
exit="panel-exit"
default="none"
>
<div className="settings-panel">
...
</div>
</ViewTransition>
This separation is useful because JavaScript controls the state transition while CSS controls the visual presentation.
React's documentation recommends View Transition Classes for customizing animations rather than relying on manually assigned view-transition-name values for most cases.
Animating Page Navigation
One of the most useful applications is navigation.
Imagine an application with two pages:
function App({ page }) {
return (
<ViewTransition key={page}>
{page === "home" ? <HomePage /> : <ProfilePage />}
</ViewTransition>
);
}
The navigation itself should be performed as a Transition:
import { startTransition } from "react";
function navigateToProfile() {
startTransition(() => {
setPage("profile");
});
}
This approach lets React coordinate the DOM changes with the View Transition instead of treating the animation as a separate operation.
For applications using a router, the router can integrate navigation with React Transitions, allowing page-level View Transitions to be layered onto the navigation flow. React specifically describes navigation as one of the intended use cases for View Transitions.
Shared Element Transitions
A more advanced use case is moving the same visual element between two UI states.
For example, a product image might appear as a small thumbnail on a product list and become a large image on the product details page.
You can give the corresponding View Transitions the same name:
<ViewTransition name="product-image">
<img
src={product.image}
alt={product.name}
/>
</ViewTransition>
When the old named transition is removed and another transition with the same name is inserted during the same React Transition, React can treat them as a shared element transition.
This is useful for:
Product image expansion
Profile avatar transitions
Card-to-details interfaces
Gallery navigation
Media previews
However, names must be unique while the transition is active. Accidentally mounting two View Transitions with the same name can cause problems. React recommends using a globally unique naming strategy when explicit names are required.
Using View Transitions with Suspense
React 19.3 also integrates View Transitions with Suspense.
For example:
<ViewTransition update="auto" default="none">
<Suspense fallback={<ProductSkeleton />}>
<ProductDetails />
</Suspense>
</ViewTransition>
Join the conversation! Your thoughts help the community grow.