React ViewTransition Explained: Native UI Transitions

The ViewTransition component animates a component tree when it enters, exits, updates, or is shared between two positions, using the browser's View Transition API.

7 min read

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.

Canary API

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.

App.jsxApp.jsx
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.

TriggerWhen it fires
enterThe ViewTransition is inserted in this transition
exitThe ViewTransition is deleted in this transition
updateIts content mutates or its size and position change
shareA 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.

App.jsxApp.jsx
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.

App.jsxApp.jsx
<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.

csscss
::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

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.
RunePowered by Rune AI

Frequently Asked Questions

When does ViewTransition activate?

Only for updates wrapped in a Transition, Suspense, or useDeferredValue. A plain setState updates immediately and does not trigger an animation.

Does ViewTransition respect prefers-reduced-motion?

Not automatically. React recommends disabling or toning down the animations yourself with the prefers-reduced-motion media query.

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.