React 19.3 Makes View Transitions Stable: Plus Fragment Refs and a First-Class browser() API
React 19.3 is out with stable View Transitions, Fragment Refs for wrapper-free DOM access, a browser() escape hatch for SSR, and Trusted Types support. Here's what to adopt, and what to skip.

On this page
React 19.3 landed on npm this week (September 9), and it's one of those releases where the headline features have been auditioning in public for a while. View Transitions and Fragment Refs were both previewed as experimental APIs last year. Now they're stable, alongside a first-class browser() API for opting out of server rendering, Trusted Types support, and direct Context rendering in Server Components.
Related reading: Cloudflare Acquires Deno to Make Workers Self-Hosting · Meta's Astryx Brings an Eight-Year Internal React Design System to Open Source.
If you ship React for a living, here's the practical rundown.
View Transitions go stable
The new <ViewTransition> component animates elements as they enter, exit, move, or resize, using the browser's native View Transition API. The basic usage is almost suspiciously simple:
import { ViewTransition } from 'react';
{isShowing && (
<ViewTransition>
<Component />
</ViewTransition>
)}
Whenever an update marked as a Transition changes the child's style (or mounts/unmounts the boundary) React animates it, defaulting to a smooth cross-fade. React picks the animation from how the tree changed: enter, exit, update, or share (a named transition removed in one place and added in another, think shared-element morphs between routes).
Two details worth knowing:
- Only Transitions animate. Urgent updates render immediately; state updates inside
startTransition, a<Suspense>reveal, oruseDeferredValuetrigger animations. This is the right default, your UI stays snappy and only choreographed work animates. addTransitionTypecustomizes per-cause. Navigating a carousel forward vs. backward can set the same slide state but animate in opposite directions. You tag the update with a type and map types to CSS classes:
function nextSlide() {
startTransition(() => {
addTransitionType('next');
setCurrentSlide(c => c + 1);
});
}
<ViewTransition
enter={{ next: 'from-right', previous: 'from-left' }}
exit={{ next: 'to-left', previous: 'to-right' }}
>
<Page />
</ViewTransition>
The Suspense integration is the real story
The most interesting part isn't enter/exit fades. It's how View Transitions compose with Suspense. Wrap a boundary and React animates the fallback-to-content reveal, and images or fonts inside a <ViewTransition> can trigger Suspense while loading, eliminating the classic flicker-in of late resources and enabling coordinated loading sequences.
The React team's own guidance is refreshingly restrained: animate sparingly. Their recommended pattern for Suspense reveals says it all:
<ViewTransition update="auto" default="none">
<Suspense fallback={<Fallback />}>
<Component />
</Suspense>
</ViewTransition>
Fallbacks appear instantly (no animation on the way in), the reveal animates, and cached UI that resolves synchronously never animates at all. Steal this pattern wholesale.
Platform note
<ViewTransition> is DOM-only for now, React Native support is still in progress. It also had Mobile Safari and SuspenseList crash fixes in this very release, so if you experimented with the canary builds, re-test on real devices before drawing conclusions.
Fragment Refs: behavior without wrappers
The other stable graduation solves a genuinely annoying problem. When you need DOM access to a component's output (attach a listener, observe visibility, move focus), you historically needed either a wrapper <div> (which breaks layouts and CSS) or the cooperation of the component author (impossible with third-party code).
Fragment Refs let you pass a ref directly to a <Fragment> and get back a FragmentInstance, a limited, group-oriented API over the fragment's DOM children:
import { Fragment, useRef, useLayoutEffect } from 'react';
function InView({ onChange, children }) {
const fragmentRef = useRef(null);
useLayoutEffect(() => {
const observer = new IntersectionObserver((entries) => {
onChange(entries.some((e) => e.isIntersecting));
});
const instance = fragmentRef.current;
instance.observeUsing(observer);
return () => instance.unobserveUsing(observer);
}, [onChange]);
return <Fragment ref={fragmentRef}>{children}</Fragment>;
}
The instance API covers events (addEventListener/removeEventListener/dispatchEvent on first-level children), focus traversal (focus, focusLast, blur), observers (observeUsing/unobserveUsing for Intersection and Resize observers), and measurement (getClientRects, scrollIntoView, and friends). No wrapper divs, no forked library components. If you maintain a design system, this one is worth adopting early. It removes an entire class of "please forward our ref" support tickets.
browser(): an honest SSR escape hatch
Everyone has written the typeof window !== 'undefined' check or the mount-flag effect. React 19.3 replaces the folklore with a first-class API, a component calls use(browser()) to opt out of server rendering:
import { use } from 'react';
import { browser } from 'react-dom';
function TimeZone() {
use(browser());
return <p>{new Intl.DateTimeFormat().resolvedOptions().timeZone}</p>;
}
On the server this suspends to the nearest Suspense fallback; on the client it renders normally. Because it's just use(), it works conditionally, render a default value in HTML when you have one, suspend only when you don't. The canonical upgrade path this unlocks: data-fetching hooks that skip SSR unless initial data was provided by a Server Component or loader.
Also in this release
- Trusted Types support. React no longer coerces values to strings before DOM injection, so
TrustedHTML/TrustedScriptobjects survive and CSPrequire-trusted-types-for 'script'policies work as intended. If you run a hardened CSP, this un-breaks React. - Context rendered directly in Server Components. No more pointless Provider wrapper components just to pass server-fetched data into the client tree.
- Independent Transitions (a slow Transition no longer holds up unrelated ones) plus Strict Mode double-invoking effects during hydration (matching client roots),
onFullscreenChange/onFullscreenError, SVGmaskType, iframecredentialless, andError.causetransport to the client.
Upgrade advice
Adopt Fragment Refs and the Suspense + View Transition pattern now. They're pure wins. Save broad <ViewTransition> usage for intentional choreography (route changes, reveals), not every state update. And if you've been carrying isMounted hacks for SSR, browser() is your cleanup ticket.








