Skip to content
Journal

Web Development · Frameworks

React 19.3: View Transitions and Fragment Refs Are Stable

React 19.3 shipped September 9, 2026, and turned two long-experimental APIs stable: View Transitions and Fragment Refs. What changed and whether to upgrade.

Abhishek Gupta

Abhishek Gupta

6 min read

Timeline showing React's View Transitions and Fragment Refs APIs moving from experimental to stable in the 19.3 release

Sponsored

Share

React 19.3 shipped on September 9, and the headline is that two APIs which have sat behind experimental flags for a while are now just… part of React. View Transitions and Fragment Refs both graduate to stable in this release, no asterisks, no “unstable_” prefix left to strip out of your imports.

Neither is a small feature. If you’ve been waiting for either one to stop moving before building on it, this is that moment.

View Transitions go stable

The <ViewTransition> component wraps the browser’s native View Transition API with React-aware coordination. Wrap a component in it, and React animates it through four cases: enter (mounting), exit (unmounting), update (changing in place), and share (moving between two positions, the classic “shared element” transition you’ve seen in native apps).

import { ViewTransition } from 'react';

{isShowing && (
  <ViewTransition>
    <ProductCard product={product} />
  </ViewTransition>
)}

The catch, and it’s a deliberate one: React only animates changes wrapped in a Transition. Setting state directly doesn’t trigger anything. You need startTransition, a Suspense reveal, or useDeferredValue in the update path. That constraint is what keeps accidental animations from firing on every re-render, but it also means retrofitting View Transitions onto an existing app means auditing where your state updates actually happen, not just dropping in a component.

19.3 adds addTransitionType on top of that, which lets you tag why a transition is happening (say, “next slide” versus “previous slide”) and pick different animations per direction:

function nextSlide() {
  startTransition(() => {
    addTransitionType('next');
    setCurrentSlide(c => c + 1);
  });
}

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

That’s the difference between “things fade” and “things fade in a direction that makes sense to the user,” and it’s the kind of detail that’s easy to skip until a designer notices the carousel animates the same way no matter which arrow you clicked.

View Transitions also hook into Suspense in a way that’s easy to miss in the changelog: wrap a <Suspense> boundary in a <ViewTransition> and set update="auto", and React animates the swap from fallback to real content automatically, without you writing any transition logic for the loading state itself. The same mechanism extends to images and fonts. Put an <img> or a <style> tag with a precedence inside the boundary, and React suspends until the asset actually loads, so the reveal animation doesn’t fire against a half-rendered image. If you’ve ever shipped a “loading” flicker because an image resolved a frame after its container did, this closes that gap without extra state.

Fragment Refs go stable

This one solves a smaller but more common annoyance: attaching behavior to a group of sibling elements without wrapping them in a <div> you only added so you’d have somewhere to put a ref.

function PostList({ posts }) {
  const fragmentRef = useRef(null);

  useEffect(() => {
    fragmentRef.current.focus();
  }, []);

  return (
    <Fragment ref={fragmentRef}>
      {posts.map(post => <Heading key={post.id}>{post.title}</Heading>)}
    </Fragment>
  );
}

The FragmentInstance you get back isn’t a DOM node, it’s a proxy that applies across everything the Fragment renders: focus, focusLast, blur, addEventListener/removeEventListener, observeUsing/unobserveUsing for IntersectionObserver and ResizeObserver, plus layout methods like getClientRects and scrollIntoView. If you’ve ever added a wrapper <div> purely to hang an IntersectionObserver off a list, this removes the div.

It’s a narrower fix than View Transitions, but it’s the kind of narrow fix that shows up constantly once you notice it. Design systems built around composable list items, accordions, and card grids tend to accumulate exactly this kind of wrapper, added once for a ref and never removed because nobody wants to be the one who breaks the CSS grid by changing the DOM structure.

The smaller changes: Context and Trusted Types

Two more changes in 19.3 are worth knowing even though neither one gets its own headline.

Context in Server Components, without the wrapper. Before 19.3, rendering Context from a Server Component required a 'use client' wrapper component whose only job was re-exporting a Provider:

// Before: a client wrapper just to provide context
'use client';
export const UserContext = createContext(null);
export function UserProvider({ currentUser, children }) {
  return <UserContext value={currentUser}>{children}</UserContext>;
}

Now the Server Component can import the Context directly and render it:

// After: no wrapper needed
import { UserContext } from './user-context';

export async function Layout({ children }) {
  const currentUser = await getCurrentUser();
  return <UserContext value={currentUser}>{children}</UserContext>;
}

It’s a small change on paper, but if you’ve built out a Next.js App Router or similar RSC-based app since 19.0, you’ve almost certainly got two or three of these wrapper components sitting around for no reason other than “Context needs a client boundary.” You can delete them now.

There’s also browser() (from react-dom), which lets a component opt out of server rendering entirely: it shows a Suspense fallback during SSR and renders normally on the client, which is useful for anything that only makes sense in a browser context, like a TimeZone display reading Intl.DateTimeFormat. And React DOM now passes TrustedHTML, TrustedScript, and TrustedScriptURL through to DOM APIs without coercing them, so apps enforcing a Trusted Types Content Security Policy no longer fight React to do it. Neither change is headline material on its own, but both close gaps that teams running strict CSPs or SSR-heavy apps have been working around manually.

Should you upgrade?

The release notes list no breaking changes, and that tracks with what these features are: two stable graduations, a handful of additive React DOM behaviors (fullscreen events, fetchPriority for module resources, submitter on submit events), and a long tail of bug fixes to useDeferredValue, Suspense hydration, and useSyncExternalStore. If you’re anywhere on the 19.x line, this is a version bump you run in CI and move on, not a migration you schedule a sprint around.

Whether you adopt View Transitions or Fragment Refs immediately is the more interesting question, and it depends on what you were already doing. If your app has been reaching for a third-party animation library just to get shared-element transitions between routes, or you’ve got a page-transition setup built on framer-motion that’s mostly fighting React’s render cycle, this is worth a spike. If you weren’t already fighting that problem, stabilization mostly means one less experimental API to worry about disappearing.

For teams running Next.js on top of React, pair this with the App Router changes in Next.js 15/16 before deciding how deep to go with View Transitions, since route-level transitions interact directly with how the framework handles navigation and Suspense boundaries. And if state management in your app is already fighting Context propagation, it’s worth reading up on where Zustand and Jotai fit next to React’s built-in primitives before you reach for another wrapper component to solve it.

Frequently asked questions

What's new in React 19.3?
React 19.3, released September 9, 2026, stabilizes the View Transitions and Fragment Refs APIs, adds a browser() function for opting components out of server rendering, adds Trusted Types support for DOM XSS prevention, and lets Server Components render Context directly without a client wrapper. It ships no breaking changes.
Do View Transitions in React 19.3 replace CSS view transitions?
No, they wrap the browser's native View Transition API rather than replacing it. React's <ViewTransition> component adds coordination on top: it only animates changes marked as Transitions (via startTransition, a Suspense reveal, or useDeferredValue), and it exposes enter, exit, update, and share animation types you can target with CSS.
What is a Fragment Ref and why would I use one?
A Fragment Ref is a ref attached to a <Fragment> instead of a single DOM node. It returns a FragmentInstance with methods like focus, addEventListener, and observeUsing, which apply across every element the Fragment renders. It's for cases where you need shared behavior, like focus management or intersection observation, across a list of siblings without wrapping them in an extra div just to have somewhere to put the ref.
Are there breaking changes in React 19.3?
No breaking changes are listed in the 19.3 release notes. It's a normal minor-version upgrade from 19.2, and the changes are additive: two newly stable APIs, a handful of new React DOM behaviors, and bug fixes.
Should I upgrade to React 19.3 now?
If you're already on 19.x, yes, it's a low-risk upgrade with no breaking changes. Whether you need to adopt View Transitions or Fragment Refs immediately is a separate question. Both were already usable behind experimental flags, so if you weren't using them before, stabilization mainly means you can now rely on the API shape without worrying about it shifting under you.

Sources

Sponsored

Sponsored

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored