React 19.3 shipped on September 9 with no breaking changes and five features worth your attention. Two of them — ViewTransition and Fragment refs — just graduated from experimental to stable after months in canary. Two more solve structural problems that have been frustrating React developers since Server Components became mainstream. Here’s what to adopt immediately, what to hold, and what’s only relevant in narrow situations.
ViewTransition: Drop the Animation Library for Most Use Cases
The <ViewTransition> component is now stable and it does what it says: wrap any element in it and React animates it in, out, or across the page using the browser’s native View Transition API. No JavaScript animation runtime, no CSS keyframe boilerplate, no Framer Motion import.
import { ViewTransition, startTransition } from 'react';
{showItem && (
<ViewTransition>
<Component />
</ViewTransition>
)}
Animations only fire for updates wrapped in startTransition — urgent UI updates (typing, instant-feedback clicks) stay snappy. For directional animations like a carousel that should slide left going forward and right going back, the new addTransitionType function pairs with ViewTransition’s enter and exit props:
import { ViewTransition, startTransition, addTransitionType } from 'react';
function nextSlide() {
startTransition(() => {
addTransitionType('next');
setCurrentSlide(c => c + 1);
});
}
<ViewTransition enter={{'next': 'from-right'}} exit={{'next': 'to-left'}}>
<Page />
</ViewTransition>
Browser support is solid: Chrome 111+, Edge 111+, Safari 18+, Firefox 144+. Same-document transitions have been Baseline since October 2025. Cross-document transitions (full-page navigations in multi-page apps) are still Chrome and Edge only — but graceful degradation is built in; browsers that don’t support the API skip the animation and render normally.
The honest take on bundle cost: Motion (formerly Framer Motion) costs 85KB. GSAP costs 78KB. ViewTransition costs zero because it’s the browser. For the 80% of animation use cases that are “animate this thing in and out,” ViewTransition is now the right answer. For scroll-driven animations, complex timelines, or physics-based interactions, GSAP still wins.
Fragment Refs: Stop Wrapping Siblings in Invisible Divs
React Fragments have existed since 2017 precisely so you don’t need wrapper elements. Then developers discovered they couldn’t attach refs to them and started adding wrapper divs anyway, defeating the purpose. That problem is now fixed.
In React 19.3, you can pass a ref directly to a Fragment. React returns a FragmentInstance — not a DOM node, but a proxy that exposes DOM-like APIs across the fragment’s first-level children:
import { useRef, useEffect, Fragment } from 'react';
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>
);
}
FragmentInstance supports addEventListener, removeEventListener, focus, focusLast, blur, observeUsing (IntersectionObserver and ResizeObserver), scrollIntoView, and getClientRects. The proxy targets first-level children — if your Fragment wraps deeply nested elements, you still need a different approach. But for grouping sibling elements and managing them as a unit, this is exactly what developers have been asking for.
browser(): Retire the Mounted State Hack
If you’ve built anything with React Server Components and browser-only APIs — localStorage, Intl, geolocation — you’ve written this at some point:
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return null;
The new browser() function from react-dom makes this unnecessary. Call use(browser()) inside any component and React skips SSR for it, shows a Suspense fallback on the server, and renders normally on the client — no hydration mismatch, no extra state, no wasted render cycle:
import { use } from 'react';
import { browser } from 'react-dom';
function TimeZone() {
use(browser());
const timeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone;
return <p>Your timezone: {timeZone}</p>
}
The mounted pattern is dead. If your codebase has several of these, React 19.3 is a good opportunity to clean them up.
Trusted Types and Server Component Context
React 19.3 now cooperates with the browser’s Trusted Types API — the Content Security Policy mechanism that prevents DOM-based XSS by requiring sanitized types before DOM injection. Previously, React would coerce TrustedHTML objects to strings, breaking Trusted Types policies. Now it passes them through correctly. If your app already has a Trusted Types CSP policy, this is a free security upgrade. If it doesn’t, it’s worth evaluating.
Server Components can now render Context directly from 'use client' modules without a wrapper Provider component. It reduces one file in the standard context setup pattern and is a welcome ergonomic improvement in RSC-heavy codebases — though not worth refactoring specifically for.
How to Upgrade
React 19.3 has zero breaking changes. Update both packages and you’re done:
npm install react@19.3.0 react-dom@19.3.0
ViewTransition and Fragment refs are opt-in — nothing breaks until you use the new APIs. The browser() function requires a Suspense boundary if used inside an RSC tree. Trusted Types works automatically if your CSP policy is already in place. The full release notes with bug fixes and new DOM events are in the official React 19.3 announcement.













