Skip to content

fix: keep virtualized async ListBox focus visible#10165

Open
000vincent999 wants to merge 4 commits into
adobe:mainfrom
000vincent999:fix-10129-listbox-virtualized-end-key
Open

fix: keep virtualized async ListBox focus visible#10165
000vincent999 wants to merge 4 commits into
adobe:mainfrom
000vincent999:fix-10129-listbox-virtualized-end-key

Conversation

@000vincent999

Copy link
Copy Markdown

Closes #10129

Summary

This fixes keyboard navigation for virtualized async ListBox collections when pressing End or paging after more items have loaded.

Previously, focusedKey could move to the newest last item, but ListLayout did not always compute layout info for that focused/persisted key. The old fallback only forced a full layout when requestedRect.area < contentSize.area. Because the virtualizer overscans on both axes, a vertical list can get extra cross-axis width in requestedRect, making the area comparison look large enough even though the newly loaded focused item has not been laid out. As a result, the item was not mounted and could not be scrolled into view.

The fix makes ListLayout key-driven for this fallback: if a persisted key is missing from layoutNodes but exists in the current collection, it computes the full layout so the focused item can be rendered. This avoids relying on cross-axis-sensitive area comparisons. The change also refreshes the virtualizer visible rect after programmatic focus scrolling and re-scrolls virtualized collections when the collection object changes while the focused key remains the same.

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change.
  • Filled out test instructions.
  • Updated documentation. Not applicable for this behavior-only bug fix.
  • Looked at the Accessibility Practices for this feature. This preserves the expected ListBox keyboard navigation behavior.

📝 Test Instructions:

  • yarn jest packages/react-stately/test/virtualizer/ListLayout.test.ts --runInBand
  • yarn vitest --config=vitest.browser.config.ts packages/react-aria-components/test/ListBox.browser.test.tsx

Local verification:

  • Passed: yarn jest packages/react-stately/test/virtualizer/ListLayout.test.ts --runInBand
  • Not completed locally: the browser test command requires Playwright browser binaries, and this environment reported missing Chromium/Firefox/WebKit executables. Run yarn playwright install before the browser test command.

🧢 Your Project:

Personal contribution

);
let refreshVisibleRectRef = useContext(RefreshVisibleRectContext);
if (refreshVisibleRectRef) {
refreshVisibleRectRef.current = refreshVisibleRect;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks the rules of hooks, cannot read or write to a ref during render

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the hook rule violation by moving the refreshVisibleRectRef.current update into a layout effect and clearing it on cleanup.

I also tried narrowing this to only the ListLayout persisted-key fix by removing the virtualizer refresh path, but the browser regression still failed: focus moved to the newly loaded item, while the item remained outside the listbox viewport. So the refresh path is still needed to keep the virtualizer visible rect in sync after programmatic scrolling.

Verified with:

yarn jest packages/react-stately/test/virtualizer/ListLayout.test.ts --runInBand
nvm use 22.22.2
yarn vitest --config=vitest.browser.config.ts packages/react-aria-components/test/ListBox.browser.test.tsx --run

@snowystinger snowystinger left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the interest. This is a lot of very involved changes, maybe it'd be good to write up a more formal RFC for this to get comments on the direction. https://github.com/adobe/react-spectrum/blob/main/rfcs/template.md

@000vincent999

000vincent999 commented Jul 7, 2026

Copy link
Copy Markdown
Author

Opened an RFC PR for the broader virtualizer visible rect refresh direction: #10309.

This implementation PR is still useful as a concrete reference for the proposed approach and regression coverage, but I’m happy to pause/adjust it based on the RFC discussion.

The RFC separates the fix into two parts:

  1. ListLayout persisted-key layout computation.
  2. Refreshing the virtualizer visible rect after focus-driven programmatic scrolling.

For context, I also tested narrowing this implementation to only the ListLayout fix, but the browser regression still failed because focus moved to the newly loaded item while it remained outside the listbox viewport.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ListBox keyboard navigation with Virtualizer fails to focus and scroll to last item during async loading

3 participants