A ref needs a node, and a component that returns several siblings has no single node to give. The old fix was a wrapper <div> added purely so something could be measured or observed. React 19.3 7,897 , released on 9 September 2026, removed the need for it: Fragment now accepts a ref and fills it with a fragment instance, an object that forwards a small set of operations to the children it wraps.
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone@8.0.5/babel.min.js"></script>
<script type="importmap">{"imports":{"react":"https://esm.sh/react@19.3.0?dev",
"react-dom/client":"https://esm.sh/react-dom@19.3.0/client?dev"}}</script>
<div id="root"></div><script type="text/babel" data-type="module">
import React, { Fragment, useRef, useState } from 'react';
import { createRoot } from 'react-dom/client';
function Fields({ children }) {
const group = useRef(null), [info, setInfo] = useState('');
function inspect() {
const rects = group.current.getClientRects();
group.current.focus(); // first focusable child, depth-first
setInfo(`${rects.length} children, first ${Math.round(rects[0].width)}px wide; `
+ `focus is on #${document.activeElement.id}`);
}
return <div style={{font: '15px system-ui'}}>
<Fragment ref={group}>{children}</Fragment>
<p><button onClick={inspect}>Inspect the group</button></p>
<p><b>{info}</b></p></div>;
}
createRoot(document.getElementById('root')).render(<Fields>
<input id="first" placeholder="first" /><input id="last" placeholder="last" /></Fields>);
setTimeout(() => document.querySelector('button').click(), 400);
</script>
The readout comes entirely from the fragment instance: getClientRects() returns one DOMRect per first-level DOM child, and focus() walks the children depth-first to the first focusable element. The same object offers focusLast(), blur(), addEventListener and removeEventListener (attached to every first-level child), observeUsing and unobserveUsing for an IntersectionObserver or ResizeObserver, dispatchEvent, scrollIntoView, getRootNode and compareDocumentPosition.
Generic wrappers become possible without touching the markup they wrap: a <Highlight> that watches whatever you put inside it for visibility no longer has to impose a <div> that might break a grid or a flex layout.