React applications often need direct access to DOM elements for tasks such as handling focus, observing visibility, measuring layout, or attaching browser event listeners.
For a single element, a normal ref is straightforward:
const inputRef = useRef(null);
<input ref={inputRef} />
The problem becomes more interesting when a component renders multiple sibling elements.
For example:
function CardGroup() {
return (
<>
<article>First card</article>
<article>Second card</article>
<article>Third card</article>
</>
);
}
There is no single DOM element representing the entire group.
Before React 19.3, developers often had to introduce a wrapper element, expose multiple refs, or modify child components to make DOM access possible.
React 19.3 makes this considerably easier with Fragment Refs.
A ref can now be attached directly to an explicit <Fragment>, giving the application a FragmentInstance that provides a controlled set of operations across the Fragment's DOM children. Fragment Refs became stable in React 19.3.
The Problem With Referencing Multiple Siblings
Consider a component that renders a collection of headings:
function PostList({ posts }) {
return (
<>
{posts.map(post => (
<h2 key={post.id}>
{post.title}
</h2>
))}
</>
);
}
Suppose another component needs to:
Add an event listener to the headings.
Observe when the headings become visible.
Measure their positions.
Scroll them into view.
Manage focus across the group.
A normal DOM ref cannot represent the whole sibling group.
One option is to add a wrapper:
function PostList({ posts }) {
return (
<div ref={containerRef}>
{posts.map(post => (
<h2 key={post.id}>
{post.title}
</h2>
))}
</div>
);
}
That solves the ref problem, but it changes the DOM structure.
Sometimes that is harmless.
Sometimes it is not.
An additional element can affect:
CSS selectors
Flexbox layouts
Grid layouts
Accessibility semantics
Styling
Existing DOM assumptions
Third-party integrations
Fragment Refs provide another option without requiring that wrapper.
What Is a Fragment Ref?
React 19.3 allows a ref to be passed to an explicit <Fragment>.
import { Fragment, useRef } from "react";
function PostList({ posts }) {
const fragmentRef = useRef(null);
return (
<Fragment ref={fragmentRef}>
{posts.map(post => (
<h2 key={post.id}>
{post.title}
</h2>
))}
</Fragment>
);
}
Instead of receiving an HTMLElement, the ref receives a FragmentInstance.
Fragment
|
+-- <h2>
+-- <h2>
+-- <h2>
The Fragment itself still does not create a DOM element.
The ref provides an API for interacting with the DOM children represented by the Fragment.
Why You Need an Explicit Fragment
There is an important syntax difference.
This shorthand:
<>
<h2>First</h2>
<h2>Second</h2>
</>
cannot receive a Fragment ref.
Instead, use the explicit form:
import { Fragment } from "react";
<Fragment ref={fragmentRef}>
<h2>First</h2>
<h2>Second</h2>
</Fragment>
The explicit <Fragment> is also the form required when you need to provide a key to a Fragment.
Understanding FragmentInstance
The value stored in the ref is not a normal DOM element.
For example:
const fragmentRef = useRef(null);
console.log(fragmentRef.current);
After mounting, fragmentRef.current represents the Fragment instance.
The FragmentInstance API provides operations for common DOM-related tasks, including:
Event listeners
Event dispatching
Focus management
Intersection and resize observation
Layout measurement
Root-node access
Document-position comparison
Scrolling
This gives developers lower-level DOM control while preserving the Fragment's zero-wrapper behavior.
Adding Events to Multiple Children
One practical use case is attaching the same event listener to a group of elements.
Consider:
function ClickableGroup({ children, onClick }) {
const fragmentRef = useRef(null);
useEffect(() => {
const fragment = fragmentRef.current;
if (!fragment) {
return;
}
fragment.addEventListener("click", onClick);
return () => {
fragment.removeEventListener("click", onClick);
};
}, [onClick]);
return (
<Fragment ref={fragmentRef}>
{children}
</Fragment>
);
}
You can then use it like this:
<ClickableGroup
onClick={() => {
console.log("Group child clicked");
}}
>
<button>Save</button>
<button>Share</button>
<button>Archive</button>
</ClickableGroup>
The listener is applied to the Fragment's first-level DOM children.
If children are added or removed, React keeps the Fragment's listener behavior synchronized with those children.
First-Level DOM Children Matter
One of the most important details about Fragment Refs is understanding what they target.
Consider:
<Fragment ref={fragmentRef}>
<div id="first" />
<Wrapper>
<div id="nested" />
</Wrapper>
<div id="last" />
</Fragment>
The Fragment's first-level host children are effectively:
Fragment
|
+-- #first
|
+-- #nested's outer DOM element
|
+-- #last
The deeply nested descendants are not directly targeted by methods such as addEventListener(), observeUsing(), and getClientRects(). React looks through components to identify the relevant first-level DOM children, but does not recursively target every descendant for those methods.
This distinction is important when designing components around Fragment Refs.
Managing Focus Across a Fragment
Fragment Refs are also useful for focus management.
For example:
function NavigationGroup() {
const fragmentRef = useRef(null);
return (
<>
<button
onClick={() => {
fragmentRef.current?.focus();
}}
>
Focus navigation
</button>
<Fragment ref={fragmentRef}>
<a href="/home">Home</a>
<a href="/products">Products</a>
<a href="/contact">Contact</a>
</Fragment>
</>
);
}
The focus() method searches through the Fragment's children for a focusable element.
There is also:
fragmentRef.current?.focusLast();
which focuses the last appropriate focusable element.
And:
fragmentRef.current?.blur();
can remove focus from the relevant elements.
Unlike methods that target first-level host children, focus() and focusLast() search nested children depth-first.
Building an InView Component
A more advanced example is creating a reusable visibility component.
Suppose you want a component that notifies its parent when any of its child elements enters or leaves the viewport.
You can use IntersectionObserver with a Fragment Ref:
function InView({ children, onChange }) {
const fragmentRef = useRef(null);
useEffect(() => {
const fragment = fragmentRef.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={fragmentRef}>
{children}
</Fragment>
);
}
This creates a component that can add visibility behavior to multiple children without requiring a wrapper.
Using ResizeObserver
The same observeUsing() API can work with a ResizeObserver.
For example:
function ResponsiveGroup({ children }) {
const fragmentRef = useRef(null);
useEffect(() => {
const fragment = fragmentRef.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();
};
}, []);
return (
<Fragment ref={fragmentRef}>
{children}
</Fragment>
);
}
This can be useful for reusable components that need to monitor several sibling elements.
One limitation is that observeUsing() does not work on text nodes. React warns about this during development when a Fragment contains only text children.
Measuring Multiple Elements
Fragment Refs also provide getClientRects().
const rects = fragmentRef.current?.getClientRects();
console.log(rects);
The method returns an array of DOMRect objects representing the bounding rectangles of the Fragment's first-level DOM children.
For example:
function MeasureGroup() {
const fragmentRef = useRef(null);
const measure = () => {
const rects = fragmentRef.current?.getClientRects();
if (!rects) {
return;
}
for (const rect of rects) {
console.log({
top: rect.top,
left: rect.left,
width: rect.width,
height: rect.height
});
}
};
return (
<>
<button onClick={measure}>
Measure
</button>
<Fragment ref={fragmentRef}>
<div>First</div>
<div>Second</div>
<div>Third</div>
</Fragment>
</>
);
}
This is useful for components such as:
Custom layout systems
Drag-and-drop interfaces
Visual editors
Multi-element animations
Position-aware UI
Virtualized interfaces
Scrolling a Fragment Into View
You can also use:
fragmentRef.current?.scrollIntoView();
This provides a convenient way to bring the Fragment's children into view without introducing a wrapper element.
There is an important API detail: Fragment scrollIntoView() does not accept an options object like the native Element.scrollIntoView() API. It uses the alignToTop boolean instead.
For example:
fragmentRef.current?.scrollIntoView(true);
Do not assume that this is equivalent to:
element.scrollIntoView({
behavior: "smooth"
});
The Fragment API has its own contract.
Comparing Fragment Position
Fragment Refs also provide:
fragmentRef.current?.compareDocumentPosition(otherNode);
This can be useful when building components that need to understand the relative document position of a group of DOM elements.
For example, a reusable UI component may need to determine whether its rendered content appears before or after another element in the document.
This is a more specialized use case, but it demonstrates that Fragment Refs are intended as a controlled DOM interaction API rather than simply a replacement for an ordinary element ref.
Accessing the Root Node
Another available method is:
const root = fragmentRef.current?.getRootNode();
The result corresponds to the root containing the Fragment's parent DOM node.
Depending on the rendering environment, this can be a Document, ShadowRoot, or the Fragment instance itself when there is no parent DOM node.
This can be useful for integrations involving:
Shadow DOM
Browser APIs
Custom rendering environments
DOM-based libraries
A Reusable Event Group Component
A practical reusable component can combine Fragment Refs with React's lifecycle management.
import {
Fragment,
useEffect,
useRef
} from "react";
export function EventGroup({
children,
onClick
}) {
const fragmentRef = useRef(null);
useEffect(() => {
const fragment = fragmentRef.current;
if (!fragment) {
return;
}
fragment.addEventListener("click", onClick);
return () => {
fragment.removeEventListener("click", onClick);
};
}, [onClick]);
return (
<Fragment ref={fragmentRef}>
{children}
</Fragment>
);
}
Usage:
<EventGroup
onClick={() => {
console.log("Child clicked");
}}
>
<button>One</button>
<button>Two</button>
<button>Three</button>
</EventGroup>
The important pattern is:
Create ref
|
v
Access FragmentInstance
|
v
Attach behavior
|
v
Clean up in effect
This keeps DOM-side effects inside the appropriate React lifecycle.
Fragment Refs Versus Wrapper Elements
The difference becomes clearer when comparing the two approaches.
Requirement | Wrapper Element | Fragment Ref |
|---|---|---|
Group multiple children | Yes | Yes |
Adds DOM element | Yes | No |
Attach group event behavior | Yes | Yes |
Observe children | Yes | Yes |
Manage focus | Possible | Built in |
Preserve existing DOM structure | No | Yes |
Useful with sibling components | Yes | Yes |
Requires explicit Fragment | No | Yes |
Fragment Refs are particularly useful when adding a wrapper would change the structure of the rendered UI.
Fragment Refs Versus Multiple Individual Refs
Another approach is maintaining a ref for every child.
const refs = useRef([]);
return posts.map((post, index) => (
<article
key={post.id}
ref={element => {
refs.current[index] = element;
}}
>
{post.title}
</article>
));
This provides detailed control, but it also creates more application code.
You now need to manage:
Ref arrays
Element lifecycle
Ordering
Dynamic children
Cleanup
Event attachment
Observer registration
Fragment Refs are more convenient when the desired behavior applies to the group rather than requiring independent manipulation of every element.
When Fragment Refs Are a Good Fit
Fragment Refs are especially useful when:
You Have Multiple Sibling Elements
For example:
<Fragment ref={ref}>
<Card />
<Card />
<Card />
</Fragment>
You Cannot Modify Child Components
A reusable component may not expose its internal DOM node through a ref.
Fragment Refs can allow the parent component to apply supported behavior to the rendered children without changing the child implementation. React specifically highlights this as a use case.
A Wrapper Would Break Layout
If adding a <div> would interfere with an existing flex or grid structure, a Fragment Ref avoids the additional DOM node.
You Need Group-Level DOM Behavior
Visibility observation, focus management, measurement, and event handling are good examples.
When Not to Use Fragment Refs
Fragment Refs are not a replacement for normal React state or props.
Do not use them simply because they are new.
If the UI behavior can be expressed naturally through React state:
const [active, setActive] = useState(false);
prefer that approach.
Refs are an escape hatch for interacting with external systems and browser APIs. They should not become the primary mechanism for application state management.
Similarly, if a component needs direct control of one specific DOM element, a normal element ref is usually simpler.
Common Mistakes
Using the Shorthand Fragment
This does not provide a place for the ref:
<>
<div />
<div />
</>
Use:
<Fragment ref={fragmentRef}>
<div />
<div />
</Fragment>
Assuming FragmentInstance Is an HTMLElement
This is incorrect:
fragmentRef.current.style.display = "none";
A FragmentInstance is not a DOM element.
Use the supported FragmentInstance methods instead.
Expecting Every Method to Traverse the Entire DOM Tree
Methods such as addEventListener(), observeUsing(), and getClientRects() work with first-level host children.
Do not assume they automatically target every deeply nested DOM element.
Forgetting Cleanup
When attaching listeners or observers, clean them up:
return () => {
fragment.removeEventListener("click", onClick);
};
This prevents stale handlers and unnecessary resource usage.
Using Refs for Application State
Avoid building application logic around:
ref.current.someState
when the value actually affects rendering.
Use React state for rendering state and refs for imperative integrations.
Performance Considerations
Fragment Refs can reduce unnecessary DOM wrappers, but they do not automatically make every operation faster.
For example, observing hundreds of elements can still be expensive.
If many components require visibility tracking, consider sharing observers where appropriate.
React's Fragment Ref API also supports a reactFragments property on first-level DOM children. This allows a shared observer strategy to determine which Fragment instances contain an intersecting element.
The important performance principle is:
Avoid unnecessary DOM
+
Avoid unnecessary observers
+
Reuse expensive browser resources
Fragment Refs help with the first part, while good browser API design addresses the other two.
Best Practices
When using Fragment Refs in React 19.3:
Use an explicit
<Fragment>when you need a ref.Use Fragment Refs for imperative DOM interactions, not application state.
Prefer normal element refs when only one DOM node is involved.
Use Fragment Refs when a wrapper element would change the DOM structure.
Understand the difference between first-level children and deeply nested descendants.
Clean up event listeners and observers in effects.
Use
focus()andfocusLast()when group-level focus management is required.Use
getClientRects()for supported group-level measurement.Do not treat
FragmentInstanceas an HTMLElement.Share observers when large numbers of components need the same observation strategy.
Test dynamic child insertion and removal.
Keep browser-specific behavior isolated inside reusable components.
Conclusion
React 19.3's Fragment Refs solve a practical limitation that has existed whenever a component needs DOM-level control over multiple sibling elements without introducing an additional wrapper.
The API provides a FragmentInstance that supports common operations such as event handling, focus management, observation, measurement, scrolling, and DOM-position queries.
The biggest advantage is architectural rather than syntactic:
Before
Component
|
v
Wrapper Element
|
+-- Child
+-- Child
+-- Child
With Fragment Refs:
Component
|
v
Fragment Ref
|
+-- Child
+-- Child
+-- Child
The DOM structure remains unchanged while the component gains controlled access to the group of rendered elements.
For applications involving complex layouts, reusable components, visibility tracking, focus management, or browser API integrations, Fragment Refs provide a cleaner alternative to adding artificial wrapper elements or maintaining large collections of individual refs.
The key is to use them where imperative DOM behavior is genuinely required while keeping normal application state and rendering logic inside React's declarative model.

Join the conversation! Your thoughts help the community grow.