The React ViewTransition component animates a component tree when it enters, exits, updates, or is shared between two positions. It uses the browser's native View Transition API under the hood, so React handles the coordination and you control the styling.
ViewTransition is available only in React's Canary and Experimental channels as part of React 19.2. It currently works in the DOM only, and React Native support is in progress.
Animate an element entering and leaving
Wrap a subtree in ViewTransition, and toggle it inside a transition. React decides whether an enter or exit animation runs.
import { ViewTransition, useState, startTransition } from "react";
export default function App() {
const [show, setShow] = useState(false);
return (
<div>
<button onClick={() => startTransition(() => setShow(!show))}>
Toggle
</button>
{show && (
<ViewTransition enter="auto" exit="auto" default="none">
<div className="card">Hi</div>
</ViewTransition>
)}
</div>
);
}The card cross-fades in when show flips true and fades out when it flips false. The update must happen inside startTransition, because plain setState does not activate the animation. ViewTransition must sit before any DOM node it wraps, so an extra wrapper div stops the enter and exit triggers from firing.
The browser handles the interpolation, so the animation stays smooth even while React re-renders the subtree. React calls startViewTransition itself, so you never invoke the browser API directly.
The four animation triggers
React chooses the animation based on what changed in the transition.
| Trigger | When it fires |
|---|---|
| enter | The ViewTransition is inserted in this transition |
| exit | The ViewTransition is deleted in this transition |
| update | Its content mutates or its size and position change |
| share | A named ViewTransition moves from one tree to another |
The enter and exit triggers only fire when ViewTransition sits before any DOM node, so it must not be wrapped in an extra div. The default animation is the browser's smooth cross-fade. The share trigger is the exception: a named ViewTransition can be nested deep inside both the deleted and inserted trees, and React animates from the old position to the new one.
Updates only animate when they happen inside a transition, so a background data refresh does not trigger a cascade of fades. React also waits for any pending navigation and new fonts before starting, to avoid flicker mid-animation.
Keep state across an animation
Pair ViewTransition with Activity to animate a show and hide while preserving the subtree's state.
import { Activity, ViewTransition } from "react";
<Activity mode={show ? "visible" : "hidden"}>
<ViewTransition enter="auto" exit="auto" default="none">
<Counter />
</ViewTransition>
</Activity>;The counter keeps its value across the animation, because Activity hides instead of unmounting it. Without Activity, the counter would reset to zero each time it reappears, because unmounting destroys its state.
The pair gives you an animated reveal and state preservation at once. See React Activity Explained for the hide and restore mechanics.
The combination is the recommended way to animate components that must remember their place, like a sidebar or a tab panel.
Customize the animation
Pass a class name to the enter, exit, update, share, or default prop, then style that class with view transition pseudo-elements.
<ViewTransition enter="slide-in" exit="slide-out" default="none">
<div className="card">Hi</div>
</ViewTransition>React applies the class name to the animating element when the trigger fires. Style it with the view transition pseudo-elements in CSS.
::view-transition-new(.slide-in) {
animation: rise 300ms ease-out;
}
@keyframes rise {
from { transform: translateY(12px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}The class name is applied when that trigger activates, so reusable keyframes stay separate from component markup. The default cross-fade comes from the browser, so leaving the props at auto already gives a smooth result without any CSS. For Suspense-driven reveals, see React Suspense Explained, and for reduced-motion handling, see How to Respect Reduced Motion in React Animations.
For imperative control, the onEnter, onExit, onUpdate, and onShare callbacks expose the view transition pseudo-elements through the Web Animations API, and each should return a cleanup function that cancels its animation. Respect the prefers-reduced-motion media query as well, since React does not disable the animation automatically for users who ask for less motion.
Rune AI
Key Insights
- ViewTransition animates enter, exit, update, and share changes.
- It only activates inside a Transition or Suspense.
- Place it before any DOM node for enter and exit.
- Pair it with Activity to animate stateful show and hide.
- Use prefers-reduced-motion to disable animations.
Frequently Asked Questions
When does ViewTransition activate?
Does ViewTransition respect prefers-reduced-motion?
Conclusion
ViewTransition animates enter, exit, update, and shared element changes using the browser's native View Transition API. Wrap the subtree, place it before any DOM node, and respect reduced motion.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.