Discover React’s new ViewTransition component and how it leverages concurrent features to create smooth, browser-native animations. Learn enter/exit effects, shared element transitions, list filtering, and Suspense integration with practical examples and CSS customization tips.
Aurora Scharff
September 17, 2025
View transitions are coming to React as a built-in component. Instead of complex animation libraries, you'll soon be able to create smooth, browser-native animations with minimal, declarative code.
Note: ViewTransition
builds on React's concurrent features. If you're unfamiliar with useTransition
and related concepts, check out React Concurrent Features: An Overview first to understand the foundation.
ViewTransition
is an experimental React component that wraps the browser's View Transition API, automatically coordinating animations with React's rendering cycle. When wrapped around elements that change during a transition, it creates smooth animations between states.
The component works by creating snapshots of the old and new states, then animating between them using the browser's native View Transition API.
Here's the basic API:
import { unstable_ViewTransition as ViewTransition } from 'react';
<ViewTransition>
<Component />
</ViewTransition>
The component activates automatically when elements inside it change during a React transition, following these patterns:
ViewTransition
itself is added to the DOMViewTransition
itself is removed from the DOMViewTransition
s coordinate between mounting/unmounting treesFor interactive demos and the latest API reference, see the React Labs post and the official docs; both include visual examples and are recommended alongside this article.
A common use case is animating elements as they appear and disappear. When a ViewTransition
is added or removed during a React transition, React automatically triggers enter and exit animations.
Here's a collapsible video component example:
import { useState, startTransition } from 'react';
import { unstable_ViewTransition as ViewTransition } from 'react';
function App() {
const [showVideo, setShowVideo] = useState(false);
return (
<div>
<button onClick={() => startTransition(() => setShowVideo(!showVideo))}>
{showVideo ? '➖' : '➕'}
</button>
{showVideo && (
<ViewTransition>
<Video video={videos[0]} />
</ViewTransition>
)}
</div>
);
}
The startTransition
wrapper is essential here. ViewTransition
animations only activate when the state change occurs within a React transition (created by startTransition
, useDeferredValue
, or similar concurrent features). Without startTransition
, the video would appear and disappear instantly. With it, you get a smooth fade transition that feels polished and intentional.
For more sophisticated animations, you can create shared element transitions that animate the same logical element between different locations or states. This requires giving ViewTransition
s the same name
prop.
Consider a video that transitions from thumbnail to fullscreen:
function VideoThumbnail({ video, onExpand }) {
return (
<ViewTransition name="video-player">
<div onClick={onExpand}>
<img src={video.thumbnail} alt={video.title} />
</div>
</ViewTransition>
);
}
function VideoFullscreen({ video, onCollapse }) {
return (
<ViewTransition name="video-player">
<div>
<button onClick={onCollapse}>✕</button>
<video src={video.url} controls />
</div>
</ViewTransition>
);
}
function App() {
const [isFullscreen, setIsFullscreen] = useState(false);
return isFullscreen ? (
<VideoFullscreen
video={videos[0]}
onCollapse={() => startTransition(() => setIsFullscreen(false))}
/>
) : (
<VideoThumbnail
video={videos[0]}
onExpand={() => startTransition(() => setIsFullscreen(true))}
/>
);
}
When transitioning between these states, React recognizes the matching names and creates a shared element transition. The video appears to smoothly morph from thumbnail size to fullscreen, maintaining visual continuity.
The name
prop creates a connection between the unmounting and mounting ViewTransition
s. This works even when they're in completely different component trees, as long as they have the same name and the state update occurs during the same React transition.
ViewTransition
works beautifully with dynamic lists. Here's how to animate a searchable video list using useDeferredValue
:
import { useState, useDeferredValue } from 'react';
import { unstable_ViewTransition as ViewTransition } from 'react';
function VideoList({ videos }) {
const [searchTerm, setSearchTerm] = useState('');
// Deferring the search term creates a transition between the immediate
// input update and the deferred filter update, activating ViewTransition
const deferredSearchTerm = useDeferredValue(searchTerm);
const filteredVideos = videos.filter(video =>
video.title.toLowerCase().includes(deferredSearchTerm.toLowerCase())
);
return (
<div>
<input
placeholder="Search videos..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
{filteredVideos.map(video => (
<ViewTransition key={video.id}>
<VideoCard video={video} />
</ViewTransition>
))}
</div>
);
}
Each video smoothly animates in or out as the filter changes, with useDeferredValue
automatically creating the transition that activates the ViewTransition
animations.
ViewTransition
coordinates beautifully with Suspense boundaries, handling the transition from loading states to content. You can position ViewTransition
in two ways for different effects:
1. Wrapping both the fallback and content (treating the transition as an update):
<ViewTransition>
<Suspense fallback={<VideoSkeleton />}>
<Video />
</Suspense>
</ViewTransition>
This creates a smooth cross-fade from skeleton to content, treating them as different states of the same logical element.
2. Wrapping the fallback and content separately (treating them as distinct elements):
<Suspense fallback={
<ViewTransition>
<VideoSkeleton />
</ViewTransition>
}>
<ViewTransition>
<Video />
</ViewTransition>
</Suspense>
This treats them as separate elements, allowing for custom enter/exit animations as the VideoSkeleton
exits and Video
enters.
While ViewTransition
provides sensible defaults, you can customize animations using CSS View Transition classes. Instead of the default cross-fade, you can specify different animations for different activation types:
<ViewTransition
enter="slide-in"
exit="slide-out"
update="fade-slow"
>
<Content />
</ViewTransition>
Then define these animations in your global CSS using view transition pseudo-selectors:
/* Custom animation for elements exiting with class 'slide-out' */
::view-transition-old(.slide-out) {
animation: slide-out-left 300ms ease-in;
}
/* Custom animation for elements entering with class 'slide-in' */
::view-transition-new(.slide-in) {
animation: slide-in-right 300ms ease-out;
}
/* Custom animation properties for the transition group with class 'fade-slow' */
::view-transition-group(.fade-slow) {
animation-duration: 800ms;
}
@keyframes slide-out-left {
to { transform: translateX(-100%); }
}
@keyframes slide-in-right {
from { transform: translateX(100%); }
}
This approach works for any scenario, including the Suspense enter/exit pattern:
<Suspense fallback={
<ViewTransition exit="slide-down">
<VideoSkeleton />
</ViewTransition>
}>
<ViewTransition enter="slide-up">
<Video />
</ViewTransition>
</Suspense>
With corresponding CSS:
::view-transition-old(.slide-down) {
animation: slide-out-down 300ms ease-in;
}
::view-transition-new(.slide-up) {
animation: slide-in-up 300ms ease-out;
}
@keyframes slide-out-down {
to { transform: translateY(20px); opacity: 0; }
}
@keyframes slide-in-up {
from { transform: translateY(20px); opacity: 0; }
}
The skeleton slides down and fades out while the video slides up and fades in, producing a layered, dynamic effect that feels deliberate. This pattern gives you precise control over timing and motion while preserving React's simple, declarative mental model.
Many Suspense-enabled routers like Next.js will automatically trigger ViewTransition
's default cross-fade when navigating between pages, since their navigation is already wrapped in transitions.
To try View Transitions in Next.js, add the following to your next.config.ts
:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
experimental: {
viewTransition: true
}
};
export default nextConfig;
ViewTransition
brings browser-native animations to React without the complexity of traditional animation libraries. By wrapping elements at the right boundaries and using React's concurrent features, you get performant transitions that feel smooth and intentional.
Start with simple enter/exit animations, then explore shared elements and list filtering as your needs grow. The React documentation includes many more examples and use cases beyond what's covered here. Since this is still experimental, test thoroughly before considering it for production applications.
Sources:
Get the latest news and updates on developer certifications. Content is updated regularly, so please make sure to bookmark this page or sign up to get the latest content directly in your inbox.
React <ViewTransition>: Smooth Animations Made Simple
Discover React’s new ViewTransition component and how it leverages concurrent features to create smooth, browser-native animations. Learn enter/exit effects, shared element transitions, list filtering, and Suspense integration with practical examples and CSS customization tips.
September 17, 2025
Aurora Scharff
The Angular Compiler: From Your Code to Browser Code
This article demystifies what Angular actually bundles into your browser—including how your component and template code gets compiled into a hashed, minified main-xxxxx.js file for cache-busting—and shows how disabling optimization in angular.json lets you peek at the more human-readable compiled output.
September 15, 2025
Alain Chautard
Just Use useTemplateRef for Template Refs
Vue 3.5's useTemplateRef eliminates template ref headaches: no more type mismatches, silent typos, or verbose component typing. Here's why it's worth the switch.
September 10, 2025
Abdel Awad
We can help you recruit Certified Developers for your organization or project. The team has helped many customers employ suitable resources from a pool of 100s of qualified Developers.
Let us help you get the resources you need.