Provide a general summary of the issue here
When using react-aria-components ListBox inside a Virtualizer, focus can be lost during keyboard navigation once virtualization starts recycling or remounting option DOM nodes.
I can reproduce this with a grid-style ListBox: navigate far enough with ArrowRight to trigger remounts, then reverse direction with ArrowLeft. I can also reproduce it with rapid discrete arrow presses, not only with key repeat.
🤔 Expected Behavior?
Keyboard navigation should keep DOM focus on the logical option being navigated to, even if virtualization remounts that option’s DOM node.
😯 Current Behavior
After navigating far enough to trigger virtualization/remounts, focus can fall away from the option entirely. In a regular DOM context this may surface as focus landing on document.body. In Shadow DOM contexts it can surface as focus falling back to the shadow host from the outer document’s perspective.
Once that happens, keyboard navigation can break unless the consumer adds local focus-restoration logic.
💁 Possible Solution
There already seems to be virtualization/remount focus-repair logic on adjacent paths:
packages/@react-aria/gridlist/src/useGridListItem.ts
packages/@react-aria/grid/src/useGridCell.ts
Both track the focused key across remounts with this pattern:
// We need to track the key of the item at the time it was last focused so that we force
// focus to go to the item when the DOM node is reused for a different item in a virtualizer.
let keyWhenFocused = useRef<Key | null>(null);
ListBox appears to go through:
packages/react-aria-components/src/ListBox.tsx
packages/@react-aria/listbox/src/useOption.ts
and useOption does not appear to have an equivalent remount-focus recovery path.
My guess is that the ListBox / useOption path needs similar handling so wrappers do not need to patch this locally.
🔦 Context
This surfaced for us in a downstream wrapper built on top of react-aria-components ListBox. It looks like an upstream gap rather than something wrapper-specific.
If helpful, I can extract a smaller standalone reproduction from the wrapper into a minimal sandbox.
🖥️ Steps to Reproduce
- Render a
ListBox inside a Virtualizer with enough items to force DOM recycling/remounting.
- Use a grid layout so
ArrowRight / ArrowLeft move focus horizontally.
- Focus the first option.
- Navigate with
ArrowRight far enough that virtualization remounts or recycles option DOM nodes.
- Reverse direction with
ArrowLeft, or rapidly press arrows back and forth.
- Observe that focus can be lost when the currently focused option is remounted.
Minimal shape:
import {GridLayout, ListBox, ListBoxItem, Virtualizer} from 'react-aria-components';
import {Size} from '@react-stately/virtualizer';
<div style={{height: 400, width: 400, overflow: 'hidden'}}>
<Virtualizer
layout={GridLayout}
layoutOptions={{
minItemSize: new Size(80, 80),
maxItemSize: new Size(100, 100)
}}>
<ListBox
aria-label="virtualized listbox"
layout="grid"
items={items}
style={{width: '100%', height: '100%'}}>
{item => <ListBoxItem>{item.name}</ListBoxItem>}
</ListBox>
</Virtualizer>
</div>
🌍 Your Environment
Version
react-aria-components: 1.16.0
@react-aria/listbox: 3.15.3
What browsers are you seeing the problem on?
What operating system are you using?
Provide a general summary of the issue here
When using
react-aria-componentsListBoxinside aVirtualizer, focus can be lost during keyboard navigation once virtualization starts recycling or remounting option DOM nodes.I can reproduce this with a grid-style
ListBox: navigate far enough withArrowRightto trigger remounts, then reverse direction withArrowLeft. I can also reproduce it with rapid discrete arrow presses, not only with key repeat.🤔 Expected Behavior?
Keyboard navigation should keep DOM focus on the logical option being navigated to, even if virtualization remounts that option’s DOM node.
😯 Current Behavior
After navigating far enough to trigger virtualization/remounts, focus can fall away from the option entirely. In a regular DOM context this may surface as focus landing on
document.body. In Shadow DOM contexts it can surface as focus falling back to the shadow host from the outer document’s perspective.Once that happens, keyboard navigation can break unless the consumer adds local focus-restoration logic.
💁 Possible Solution
There already seems to be virtualization/remount focus-repair logic on adjacent paths:
packages/@react-aria/gridlist/src/useGridListItem.tspackages/@react-aria/grid/src/useGridCell.tsBoth track the focused key across remounts with this pattern:
ListBoxappears to go through:packages/react-aria-components/src/ListBox.tsxpackages/@react-aria/listbox/src/useOption.tsand
useOptiondoes not appear to have an equivalent remount-focus recovery path.My guess is that the
ListBox/useOptionpath needs similar handling so wrappers do not need to patch this locally.🔦 Context
This surfaced for us in a downstream wrapper built on top of
react-aria-componentsListBox. It looks like an upstream gap rather than something wrapper-specific.If helpful, I can extract a smaller standalone reproduction from the wrapper into a minimal sandbox.
🖥️ Steps to Reproduce
ListBoxinside aVirtualizerwith enough items to force DOM recycling/remounting.ArrowRight/ArrowLeftmove focus horizontally.ArrowRightfar enough that virtualization remounts or recycles option DOM nodes.ArrowLeft, or rapidly press arrows back and forth.Minimal shape:
🌍 Your Environment
Version
react-aria-components:1.16.0@react-aria/listbox:3.15.3What browsers are you seeing the problem on?
What operating system are you using?