What's New in React 19.3

What's New in React 19.3

Explore what's new in React 19.3, including stable View Transitions, Fragment Refs, browser-only rendering, Trusted Types, and RSC updates.

Aurora Scharff

Aurora Scharff

September 17, 2026

React 19.3 is now available on npm. After more than a year of testing in React's pre-release channels, View Transitions and Fragment Refs are finally available in a stable release. The release also adds a first-class way to render components only in the browser and improves support for Trusted Types and React Server Components.

This article covers the changes most React developers are likely to use:

  • <ViewTransition> and addTransitionType are now stable
  • Fragments can receive refs
  • use(browser()) can opt a component out of server rendering
  • React DOM supports Trusted Types values
  • Server Components can render Context directly

View Transitions Are Stable

<ViewTransition> coordinates UI animations with React's rendering cycle and the browser's View Transition API. It can animate content as it enters, exits, changes, moves, or appears somewhere else in the tree.

<ViewTransition> has been available in React's Canary channel since 2025 and has used its current name there for some time. React 19.3 brings it to the stable channel, so applications no longer need a Canary release to use it:

import { startTransition, useState, ViewTransition } from 'react';

function ProductDetails() {
  const [showDetails, setShowDetails] = useState(false);

  function toggleDetails() {
    startTransition(() => {
      setShowDetails((visible) => !visible);
    });
  }

  return (
    <>
      <button onClick={toggleDetails}>
        {showDetails ? 'Hide details' : 'Show details'}
      </button>

      {showDetails && (
        <ViewTransition>
          <ProductPanel />
        </ViewTransition>
      )}
    </>
  );
}

React uses the browser's default cross-fade. You can customize the animation with View Transition Classes or control it through the onEnter, onExit, onUpdate, and onShare props. React chooses which animation to run based on how the wrapped tree changed:

  • Enter when the boundary is added
  • Exit when the boundary is removed
  • Update when its content or layout changes
  • Share when named boundaries represent the same element in two places

The state update must be part of a Transition. Updates inside startTransition, Suspense reveals, and deferred updates from useDeferredValue can activate a View Transition. Urgent updates continue to appear immediately.

Different Animations for Different Actions

addTransitionType describes what caused a Transition. This is useful when the same component should animate differently depending on the user's action, such as moving a carousel forward or backward:

import {
  addTransitionType,
  startTransition,
  useState,
  ViewTransition,
} from 'react';

function Carousel({ slides }) {
  const [index, setIndex] = useState(0);
  const slide = slides[index];

  function move(direction) {
    startTransition(() => {
      addTransitionType(direction);
      setIndex((current) =>
        direction === 'next'
          ? (current + 1) % slides.length
          : (current - 1 + slides.length) % slides.length
      );
    });
  }

  return (
    <>
      <button onClick={() => move('previous')}>Previous</button>
      <button onClick={() => move('next')}>Next</button>

      <ViewTransition
        key={slide.id}
        enter={{ next: 'from-right', previous: 'from-left' }}
        exit={{ next: 'to-left', previous: 'to-right' }}
      >
        <Slide slide={slide} />
      </ViewTransition>
    </>
  );
}

View Transitions integrate with Suspense. Wrapping a Suspense boundary lets React animate the change from a fallback to the finished content.

React does not disable animations automatically for people who prefer reduced motion. Use the prefers-reduced-motion media query to remove or tone down custom animations.

Fragment Refs Are Stable

Fragments let components return several siblings without adding a wrapper element. Before React 19.3, a ref had no DOM node to target, which made it harder to observe, focus, measure, or attach listeners across the group.

You can now pass a ref to an explicit <Fragment>. React sets the ref to a FragmentInstance that represents the Fragment's DOM children:

import { Fragment, useRef } from 'react';

function Toolbar() {
  const actionsRef = useRef(null);

  return (
    <>
      <Fragment ref={actionsRef}>
        <button>Save</button>
        <button>Preview</button>
        <button>Publish</button>
      </Fragment>

      <button onClick={() => actionsRef.current?.focus()}>
        Focus first action
      </button>
    </>
  );
}

The Fragment still adds no element to the DOM. Its FragmentInstance provides a focused set of methods for working with the group:

  • focus(), focusLast(), and blur() manage focus across nested children
  • addEventListener(), removeEventListener(), and dispatchEvent() manage events on first-level DOM children
  • observeUsing() and unobserveUsing() connect an IntersectionObserver or ResizeObserver
  • getClientRects() and scrollIntoView() support measurement and scrolling

Fragment refs are useful for components that add behavior without controlling their children's markup. An InView component, for example, can observe several sibling cards without adding a <div> that changes the page layout.

The shorthand fragment syntax cannot receive a ref. Import Fragment and use <Fragment ref={ref}> when you need a FragmentInstance.

Render Components Only in the Browser

Server-rendered components sometimes need data that only exists in the browser, such as a saved value in localStorage. Checking for window during rendering can produce different server and client output, while waiting for an Effect requires an extra state update.

React 19.3 adds the stable browser API for this case. Pass the value returned by browser() to use() and place the component inside Suspense:

import { Suspense, use, useState } from 'react';
import { browser } from 'react-dom';

function SavedDraft() {
  use(browser('The draft is stored in localStorage.'));

  const [draft, setDraft] = useState(
    () => localStorage.getItem('draft') ?? ''
  );

  function handleChange(event) {
    const nextDraft = event.target.value;
    setDraft(nextDraft);
    localStorage.setItem('draft', nextDraft);
  }

  return <textarea value={draft} onChange={handleChange} />;
}

export default function Editor() {
  return (
    <Suspense fallback={<p>Loading draft...</p>}>
      <SavedDraft />
    </Suspense>
  );
}

During server rendering, React stops at use(browser()) and writes the nearest Suspense fallback to the HTML. In the browser, the call returns undefined, allowing the component to continue and read localStorage.

The Suspense boundary is required. In a React Server Components app, the call also has to be inside a Client Component. For a closer look at the SSR behavior and conditional browser rendering, see React's New browser API: Rendering Components Only in the Browser.

Trusted Types Support

React DOM now works with the browser's Trusted Types API. Trusted Types help prevent DOM-based cross-site scripting by requiring values sent to injection sinks such as innerHTML to come from an approved policy.

Previously, React converted these objects back into strings before passing them to the DOM. That removed their trusted type and caused the browser to reject them when a site enforced this Content Security Policy:

Content-Security-Policy: require-trusted-types-for 'script'

React 19.3 passes TrustedHTML, TrustedScript, and TrustedScriptURL values through without coercing them. Existing sanitization policies can now work with React DOM as intended.

This does not sanitize HTML automatically. Applications still need to define an appropriate Trusted Types policy and sanitize untrusted input before creating trusted values.

Render Context Directly From Server Components

Server Components cannot create Context, but they can provide a value for a Context created in a Client Component. Previously, the client module needed to export a wrapper component solely to render the provider.

React 19.3 removes that wrapper. Define and export the Context from a client module:

// user-context.js
'use client';

import { createContext } from 'react';

export const UserContext = createContext(null);

The Server Component can import and render it directly:

// layout.js
import { UserContext } from './user-context';

export async function Layout({ children }) {
  const currentUser = await getCurrentUser();

  return (
    <UserContext value={currentUser}>
      {children}
    </UserContext>
  );
}

This is most useful when a Server Component already owns the data and the Context only needs to make that value available to the client tree.

Upgrading to React 19.3

Install matching versions of react and react-dom:

npm install react@19.3 react-dom@19.3

If a framework manages or bundles React, follow its compatibility guidance instead of upgrading React independently.


Sources:

More certificates.dev articles

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.