Testing Accessibility with Fragment Refs
In my previous post, I introduced Fragment Refs. A reader pointed out that removing wrapper elements is nice, but accessibility needs careful testing. So let's actually test it.
1. What I want to check
Fragment Refs let you control a group of sibling DOM nodes without a wrapper element. Because the DOM structure stays flat, I want to check two things:
- Does
focus()move focus to the element I expected? - Does the tab order stay natural?
I did not test with a screen reader in this post.
2. Test setup
import { Fragment, useRef } from 'react';
function Sections({ posts }) {
const fragmentRef = useRef(null);
return (
<>
<button onClick={() => fragmentRef.current.focus()}>
Focus the first heading
</button>
<Fragment ref={fragmentRef}>
{posts.map((post) => (
<h2 key={post.id} tabIndex={0}>
{post.title}
</h2>
))}
</Fragment>
</>
);
}
I tested with React 19.3.0-canary-17eca7b0-20261006 in jsdom.
3. Results
| Check | Result |
|---|---|
focus() target |
The first <h2> (focusLast() moves focus to the last one) |
| Tab order | Natural: button → h2 → h2 → h2, following DOM order |
The DOM stays flat (<button>…</button><h2>…</h2><h2>…</h2>…), so no wrapper element appears in the tab order.
4. Pitfalls I found
- Elements without
tabIndexcan't receive focus, sofocus()skips them. If no child is focusable, nothing happens, and no error or warning is shown. - When the fragment has zero children,
focus()does nothing and doesn't throw. -
tabIndex={0}on a heading adds it to the tab order even though it isn't interactive. If you only need programmatic focus, usetabIndex={-1}:focus()still works, and the headings drop out of the tab order.
5. Conclusion
Fragment Refs keep the DOM flat, and in my tests focus() moved focus to the first focusable sibling and the tab order followed DOM order. The main risk is silent failure: if no child is focusable, focus() does nothing and reports nothing. So when the siblings are interactive, check that at least one child can actually receive focus. Screen reader behavior is not covered here and needs separate testing.
Top comments (0)