Introduction
React refs are straightforward when a component renders one DOM element.
For example:
function SearchBox() {
const inputRef = useRef(null);
return <input ref={inputRef} />;
}
The problem becomes more interesting when a component renders multiple sibling elements:
function ArticleSection() {
return (
<>
<h2>Introduction</h2>
<p>...</p>
<button>Continue</button>
</>
);
}
There is no single DOM node representing the entire group.
The traditional solution is often to add a wrapper:
<div ref={sectionRef}>
<h2>Introduction</h2>
<p>...</p>
<button>Continue</button>
</div>
But adding a wrapper can change CSS layout, semantics, accessibility behavior, or component structure.
React 19.3 introduces Fragment Refs as a stable feature. A ref can now be attached directly to an explicit <Fragment>, giving you a FragmentInstance that provides a limited set of DOM operations across the Fragment's children without adding a wrapper element.
This is particularly useful for reusable components that need to manage multiple DOM elements as a group.
What Are Fragment Refs?
A normal Fragment:
<>
<h2>Hello</h2>
<p>Welcome</p>
</>
does not create a DOM element.
React 19.3 allows an explicit Fragment to receive a ref:
import { Fragment, useRef } from "react";
function Section() {
const sectionRef = useRef(null);
return (
<Fragment ref={sectionRef}>
<h2>Hello</h2>
<p>Welcome</p>
</Fragment>
);
}
The ref points to a FragmentInstance, not to a <div> or another HTML element.
Conceptually:
React Fragment
|
+---- <h2>
|
+---- <p>
|
+---- <button>
|
v
FragmentInstance
The Fragment itself still does not add a DOM node.
React's documentation describes FragmentInstance as an object for interacting with the Fragment's DOM children.
Why Fragment Refs Matter
Consider a reusable component that renders a collection of headings:
function HeadingGroup({ posts }) {
return (
<>
{posts.map(post => (
<Heading key={post.id}>
{post.title}
</Heading>
))}
</>
);
}
Suppose another component needs to:
Focus the group
Observe visibility
Measure its children
Scroll them into view
Add event listeners
Determine their document position
Previously, the component might need to add a wrapper or require every child component to expose its own ref.
Fragment Refs provide another option:
Component
|
+---- Child A
+---- Child B
+---- Child C
|
v
FragmentInstance
This allows the parent component to work with the group without changing the DOM structure.
Basic Fragment Ref Example
Here is a simple example:
import { Fragment, useEffect, useRef } from "react";
function PostList({ posts }) {
const fragmentRef = useRef(null);
useEffect(() => {
fragmentRef.current?.focus();
}, []);
return (
<Fragment ref={fragmentRef}>
{posts.map(post => (
<article key={post.id} tabIndex={-1}>
<h2>{post.title}</h2>
<p>{post.summary}</p>
</article>
))}
</Fragment>
);
}
The Fragment ref provides group-level DOM behavior without inserting an additional element.
What Is FragmentInstance?
FragmentInstance is not a normal DOM node.
It provides specific operations designed for groups of DOM children.
React 19.3 currently provides methods including:
addEventListener
removeEventListener
dispatchEvent
focus
focusLast
blur
observeUsing
unobserveUsing
getClientRects
getRootNode
compareDocumentPosition
scrollIntoView
The React documentation defines these operations as the primary DOM capabilities exposed by Fragment Refs.
This limited API is intentional.
A Fragment does not represent a real DOM element, so React cannot simply expose every DOM API that an HTMLElement provides.
Which Elements Does the Ref Target?
This is an important detail.
Consider:
<Fragment ref={ref}>
<div id="first" />
<Wrapper>
<div id="second">
<span id="third" />
</div>
</Wrapper>
<div id="fourth" />
</Fragment>
The Fragment's first-level DOM children are effectively:
first
second
fourth
The nested:
third
is not a first-level child.
React looks through React components such as Wrapper to find their host DOM nodes, but it does not treat arbitrary nested DOM descendants as first-level Fragment children.
This distinction matters when using event listeners, observation, and measurements.
Focus Multiple Elements
One of the most useful operations is focus management.
A Fragment ref supports:
fragmentRef.current?.focus();
and:
fragmentRef.current?.focusLast();
The focus methods search through nested children depth-first for focusable elements.
For example:
function FormSection() {
const ref = useRef(null);
function focusFirstField() {
ref.current?.focus();
}
function focusLastField() {
ref.current?.focusLast();
}
return (
<>
<button onClick={focusFirstField}>
Focus first
</button>
<button onClick={focusLastField}>
Focus last
</button>
<Fragment ref={ref}>
<label>
Name
<input />
</label>
<label>
Email
<input />
</label>
</Fragment>
</>
);
}
This can be useful for keyboard navigation and reusable form sections.
Blur a Group
You can also remove focus from the Fragment's children:
fragmentRef.current?.blur();
This is useful when a component controls a multi-element interaction region.
However, avoid calling blur() simply because a component rerenders.
Focus should normally be controlled based on a user interaction or an explicit accessibility requirement.
Add Event Listeners to Multiple Children
Fragment Refs can add event listeners across first-level DOM children.
For example:
useEffect(() => {
const fragment = fragmentRef.current;
if (!fragment) {
return;
}
function handleClick(event) {
console.log("Clicked:", event.target);
}
fragment.addEventListener("click", handleClick);
return () => {
fragment.removeEventListener("click", handleClick);
};
}, []);
Conceptually:
Fragment
|
+---- Child A <--- click listener
|
+---- Child B <--- click listener
|
+---- Child C <--- click listener
React handles the group operation.
This can be useful when the child components do not expose refs themselves.
React 19.3 also includes a fix related to FragmentInstance event-listener cleanup and capture-option normalization, which is another reason to keep listener setup and cleanup symmetrical.
Observe Children With IntersectionObserver
Fragment Refs can connect an IntersectionObserver to the Fragment's DOM children.
For example:
function InView({ children, onChange }) {
const ref = useRef(null);
useEffect(() => {
const fragment = ref.current;
if (!fragment) {
return;
}
const observer = new IntersectionObserver(entries => {
const visible = entries.some(entry => entry.isIntersecting);
onChange(visible);
});
fragment.observeUsing(observer);
return () => {
fragment.unobserveUsing(observer);
observer.disconnect();
};
}, [onChange]);
return (
<Fragment ref={ref}>
{children}
</Fragment>
);
}
This allows a reusable component to observe several child elements without creating a wrapper.
React's React 19.3 announcement specifically demonstrates an InView component using Fragment Refs with an IntersectionObserver.
Observe Size Changes
The same pattern works with ResizeObserver.
useEffect(() => {
const fragment = ref.current;
if (!fragment) {
return;
}
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
console.log("Size changed:", entry.contentRect);
}
});
fragment.observeUsing(observer);
return () => {
fragment.unobserveUsing(observer);
observer.disconnect();
};
}, []);
This can be useful for components such as:
Responsive card groups
Dynamic navigation
Layout measurement
Virtualized UI
Resizable content areas
The important part is cleanup.
If you create an observer, detach it when the component unmounts or when the observer is replaced.
Measure Multiple Elements
getClientRects() returns an array of DOMRect objects representing the bounding rectangles of the Fragment's first-level DOM children.
For example:
const rects = fragmentRef.current?.getClientRects();
rects?.forEach(rect => {
console.log({
top: rect.top,
left: rect.left,
width: rect.width,
height: rect.height,
});
});
This is useful when you need to understand the geometry of a group.
For example:
Fragment
|
+---- Header -> DOMRect
|
+---- Content -> DOMRect
|
+---- Footer -> DOMRect
Instead of forcing everything into one wrapper rectangle, you receive the individual first-level rectangles.
Scroll a Fragment Into View
Fragment Refs also provide:
fragmentRef.current?.scrollIntoView();
This is useful for multi-element sections where there is no single wrapper.
For example:
function SearchResults() {
const resultsRef = useRef(null);
function showResults() {
resultsRef.current?.scrollIntoView({
behavior: "smooth",
block: "start",
});
}
return (
<>
<button onClick={showResults}>
View results
</button>
<Fragment ref={resultsRef}>
<h2>Results</h2>
<article>Result one</article>
<article>Result two</article>
</Fragment>
</>
);
}
The Fragment lets the component maintain its DOM structure without adding a container purely for scrolling.
Dispatch Events
A FragmentInstance can also dispatch an event:
fragmentRef.current?.dispatchEvent(
new Event("custom-event", {
bubbles: true,
})
);
This can be useful when a reusable component needs to expose group-level event behavior.
However, do not use custom DOM events as a replacement for normal React state and props when the interaction can be expressed naturally through React.
Use them when integration with browser APIs or other DOM-based systems makes the event model appropriate.
Fragment Refs vs Wrapper Elements
A wrapper remains perfectly valid.
The decision depends on the component's requirements.
Approach | Advantages | Trade-offs |
|---|---|---|
Wrapper element | Simple DOM API | Adds a DOM node |
Individual refs | Precise control | More ref management |
Fragment ref | Group-level DOM operations without wrapper | Limited API |
Context/state | React-native communication | Not suitable for direct DOM operations |
Use a wrapper when it has semantic or layout value.
Use a Fragment ref when the wrapper exists only because you need DOM access.
Fragment Refs vs Multiple Individual Refs
Without Fragment Refs, you might do this:
const refs = useRef([]);
return items.map((item, index) => (
<article
key={item.id}
ref={element => {
refs.current[index] = element;
}}
>
{item.title}
</article>
));
Now the parent has to maintain a collection of DOM references.
With a Fragment ref:
const ref = useRef(null);
return (
<Fragment ref={ref}>
{items.map(item => (
<article key={item.id}>
{item.title}
</article>
))}
</Fragment>
);
The API becomes group-oriented.
This is particularly useful when you do not actually need to address every element independently.
A Reusable FocusableGroup Component
Fragment Refs are well suited to reusable infrastructure components.
For example:
import { Fragment, useRef } from "react";
function FocusableGroup({ children }) {
const ref = useRef(null);
return (
<Fragment ref={ref}>
{children}
</Fragment>
);
}
You could extend the component with an imperative API or use the Fragment ref internally to implement keyboard and focus behavior.
The important architectural idea is that the component can add behavior to its children without requiring those children to expose their own DOM refs.
Accessibility Considerations
Fragment Refs can help implement accessibility behavior, but they do not automatically make a component accessible.
For example, programmatically moving focus should have a clear user-interface reason.
Be careful with:
fragmentRef.current?.focus();
if it runs automatically after every render.
Unexpected focus movement can make keyboard navigation difficult.
A better pattern is to move focus after an explicit action:
function openPanel() {
setOpen(true);
requestAnimationFrame(() => {
fragmentRef.current?.focus();
});
}
The exact implementation depends on the component's interaction model.
Also remember that a Fragment has no semantic role. If a group needs a semantic HTML element, use an appropriate element rather than choosing a Fragment only to avoid a wrapper.
Common Mistakes
Using the Shorthand Fragment
This will not work:
<>
...
</>
when you need to attach a ref.
Use the explicit form:
<Fragment ref={ref}>
...
</Fragment>
React's documentation specifies that a ref must be passed to an explicit <Fragment>.
Expecting a DOM Element
This is incorrect:
ref.current.classList.add("active");
ref.current is a FragmentInstance, not an HTMLElement.
Assuming All Descendants Are First-Level Children
Operations such as event listeners, observation, and getClientRects() operate on first-level DOM children.
Forgetting Observer Cleanup
Always pair:
observeUsing(observer);
with:
unobserveUsing(observer);
when the observer is no longer needed.
Using Fragment Refs for Everything
A normal DOM ref is still the simpler choice when a component has one DOM element.
Adding a Fragment Where a Semantic Element Is Required
A Fragment does not replace semantic HTML.
Best Practices
Use Fragment Refs when you need DOM behavior across multiple children.
Keep normal element refs for single-node interactions.
Use explicit
<Fragment>syntax when attaching a ref.Understand the difference between first-level children and deeply nested descendants.
Clean up event listeners and observers.
Avoid unnecessary programmatic focus.
Use semantic HTML when the group requires a semantic element.
Avoid exposing DOM behavior when normal React props can solve the problem.
Measure actual DOM behavior instead of assuming child geometry.
Test Fragment Ref behavior when children conditionally mount or unmount.
Keep Fragment-based utilities reusable and narrowly scoped.
When Fragment Refs Are Most Useful
Fragment Refs are especially useful for infrastructure-style components.
Examples include:
InView
|
+---- Observe children
FocusableGroup
|
+---- Manage keyboard focus
ScrollableSection
|
+---- Scroll multiple children
MeasureGroup
|
+---- Read child rectangles
EventGroup
|
+---- Attach common listeners
These components can add DOM behavior without requiring every child to expose a ref.
Production Checklist
[ ] React 19.3 is installed
[ ] Explicit Fragment is used
[ ] Fragment ref is created with useRef or callback ref
[ ] Code expects FragmentInstance, not HTMLElement
[ ] First-level DOM behavior is understood
[ ] Observer cleanup is implemented
[ ] Event listener cleanup is implemented
[ ] Programmatic focus has an accessibility reason
[ ] Semantic HTML is used where required
[ ] Conditional children have been tested
[ ] DOM measurements have been tested at different viewport sizes
[ ] Fragment Refs are used only where group-level DOM access is needed
Summary
React 19.3 makes Fragment Refs stable, giving developers a way to work with multiple DOM elements as a group without adding an artificial wrapper element.
The central API is:
<Fragment ref={fragmentRef}>
...
</Fragment>
which provides a FragmentInstance.
That instance supports a focused set of operations:
Events
Focus
Observation
Measurement
Scrolling
DOM position
The biggest practical benefit is architectural. A reusable component can add behavior to a group of children even when those children do not expose refs and when adding a wrapper would change the DOM structure.
Fragment Refs are not a replacement for ordinary refs. They are useful when the thing you need to control is a group of DOM nodes rather than one DOM node.
For production React applications, the best approach is to keep the abstraction narrow: use Fragment Refs for genuine group-level DOM behavior, maintain proper cleanup for observers and listeners, and continue using semantic HTML and ordinary refs wherever they are the simpler solution.

Join the conversation! Your thoughts help the community grow.