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:

  1. A user clicks a product.

  2. The application renders the product details.

  3. 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:

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:

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>

This can create a smooth transition when Suspense changes from its fallback content to the loaded component.

However, animation should not automatically be applied to every loading state.

For example, a skeleton that appears after a user clicks a button should normally appear immediately. Delaying it with an unnecessary animation can make the interface feel slower.

A better pattern is to animate the update from the fallback to the final content while keeping the initial fallback responsive.

<ViewTransition update="auto" default="none">
  <Suspense fallback={<ProductSkeleton />}>
    <ProductDetails />
  </Suspense>
</ViewTransition>

React's documentation specifically recommends being selective with Suspense animations so already-loaded content does not feel unnecessarily delayed.

Choosing Between CSS Animations and View Transitions

View Transitions do not replace traditional CSS animations.

The two approaches solve different problems.

Requirement

CSS Animation/Transition

React View Transition

Animate a button hover

Excellent

Usually unnecessary

Animate a loading spinner

Excellent

Not appropriate

Animate component insertion

Possible

Excellent

Animate page navigation

More manual

Excellent

Animate an element between UI states

More complex

Excellent

Animate a simple property

Excellent

Often unnecessary

Coordinate old and new UI states

Limited

Excellent

React Suspense integration

Manual

Built in

Shared element transition

Complex

Supported

A useful rule is simple: use CSS for local visual effects and View Transitions for changes between UI states.

Using Transition Types for Different Animations

Sometimes the same state update can happen for different reasons.

For example, a carousel can move forward or backward.

React provides addTransitionType() for this situation:

import {
  addTransitionType,
  startTransition
} from "react";

function nextSlide() {
  startTransition(() => {
    addTransitionType("next");
    setCurrentSlide(slide => slide + 1);
  });
}

function previousSlide() {
  startTransition(() => {
    addTransitionType("previous");
    setCurrentSlide(slide => slide - 1);
  });
}

You can then map those transition types to different View Transition classes:

<ViewTransition
  enter={{
    next: "slide-from-right",
    previous: "slide-from-left"
  }}
  exit={{
    next: "slide-to-left",
    previous: "slide-to-right"
  }}
>
  <Slide />
</ViewTransition>

This is useful when the direction or reason for a state change should influence the animation.

Respecting Reduced Motion Preferences

Animation should not be forced on every user.

Some users configure their operating system to reduce motion because animations can cause discomfort or make interfaces harder to use.

Use the prefers-reduced-motion media query:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

You can also replace an animation with a much shorter or less visually intensive effect.

React does not automatically disable View Transitions based on this preference, so the application should account for it explicitly.

Common Mistakes to Avoid

Using ViewTransition Without a Transition

This is one of the first issues developers encounter.

Simply rendering:

setOpen(true);

does not necessarily activate the View Transition.

For state updates that should participate in a View Transition, use a React Transition:

startTransition(() => {
  setOpen(true);
});

React also activates View Transitions for relevant Suspense and deferred-value updates.

Animating Everything

Adding <ViewTransition> around every component can make an application feel busy.

Use it for meaningful UI changes:

Do not use it simply because an animation is technically possible.

Using Duplicate Names

This is particularly important for shared element transitions.

Avoid:

<ViewTransition name="item">
  <Card />
</ViewTransition>

<ViewTransition name="item">
  <AnotherCard />
</ViewTransition>

when both instances can be mounted at the same time.

Instead, derive names from a stable identifier when appropriate:

<ViewTransition name={`product-${product.id}`}>
  <ProductCard product={product} />
</ViewTransition>

Ignoring Accessibility

A visually impressive transition is not automatically a better user experience.

Always consider reduced-motion preferences and test the interface with animations disabled.

Replacing All Existing Animation Code

View Transitions are not a replacement for every CSS or JavaScript animation.

A button press, progress indicator, skeleton shimmer, or small hover effect may still be better implemented with normal CSS.

Production Best Practices

Before introducing View Transitions into a production React application, consider the following:

  1. Use transitions for meaningful state changes. Do not animate every render.

  2. Keep local animations in CSS. View Transitions are most useful when the UI state itself changes.

  3. Use startTransition() for non-urgent updates that should participate in the transition.

  4. Keep shared transition names unique.

  5. Test navigation and interrupted transitions. Users may click multiple controls quickly.

  6. Respect prefers-reduced-motion.

  7. Avoid unnecessary animation around cached content.

  8. Test with real application layouts, especially when elements change position or size.

  9. Use explicit transition classes when the default cross-fade is not appropriate.

  10. Keep animations short enough that they do not make normal interaction feel slower.

Advantages and Disadvantages

Advantages

Disadvantages

Troubleshooting React View Transitions

The animation does not run

Check whether the state update is happening inside startTransition().

startTransition(() => {
  setPage("profile");
});

Also verify that the <ViewTransition> is actually participating in the resulting render.

The animation runs when you do not expect it

Check whether the View Transition is being activated by an update, Suspense reveal, or another transition-related update.

You can restrict behavior using:

<ViewTransition
  default="none"
  update="auto"
>
  <Component />
</ViewTransition>

Shared elements are not transitioning

Check that:

React treats shared transitions differently from ordinary enter and exit transitions, so layout and rendering conditions matter.

Conclusion

React 19.3 makes View Transitions a practical tool for building smoother React interfaces without requiring every animation to be managed manually.

The biggest benefit is not simply that React can animate a component. The real advantage is that React understands the relationship between the UI update and the animation. Transitions, Suspense, component insertion, removal, updates, and shared elements can all participate in the same rendering model.

For production applications, the best approach is to use View Transitions selectively. Use them for navigation, meaningful component changes, shared elements, and other UI state transitions. Keep small visual effects in CSS, respect reduced-motion preferences, and avoid adding animation where it makes an interaction feel slower.

When used with that distinction in mind, <ViewTransition> can make a React application feel significantly more connected without turning the codebase into a collection of custom animation logic.