[combobox] Add built-in virtualization support - #5173
Conversation
commit: |
Bundle size
PerformanceTotal duration: 1,061.19 ms -66.78 ms(-5.9%) | Renders: 78 (+0) | Paint: 1,639.53 ms -127.96 ms(-7.2%) No significant changes — details Check out the code infra dashboard for more information about this PR. |
✅ Deploy Preview for base-ui ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
be88837 to
37da8af
Compare
37da8af to
ddc0169
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dedd657ace
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eac7cfcdeb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aaa50a5d2f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (builtInVirtualizer) { | ||
| builtInVirtualizer.resetScroll(); |
There was a problem hiding this comment.
Preserve the filter-triggered scroll reset
When a keyboard-highlighted virtualized list is filtered and the active index remains in bounds, this reset is immediately undone. Filtering changes the row ID at that index, so ListVirtualizer's scrollToRowId layout effect treats it as a new highlighted-row request and scrolls the same numerical index back into view. For example, after navigating to index 50 in a 1,000-item list, typing a query that leaves more than 50 matches resets to 0 here and then jumps to filtered index 50, hiding the first results. The filter reset needs to cancel or suppress that highlighted-row scroll until navigation changes again.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not reproducible — the premise that the active index survives the filter does not hold.
When the user types, ComboboxInput clears the highlight unless autoHighlight is enabled (ComboboxInput.tsx, onChange: if (open && store.state.activeIndex !== null && !autoHighlightEnabled) clearHighlight()). With autoHighlight on, the highlight is deliberately kept but setInputValue immediately re-points it at getFirstEnabledIndex(...) of the new collection.
Either way scrollToRowIndex no longer refers to the pre-filter index by the time ListVirtualizer re-renders, so the scrollToRowId layout effect has nothing to scroll back to and the reset stands.
Verified with a test that navigates to index 10 in a 100-item virtualized list, types a query leaving 20 matches (so the old index stays in bounds), and asserts the scroller is still at 0 afterwards. It passes without any change; it is now in ComboboxVirtualizer.test.tsx as keeps the virtual scroller reset when filtering a keyboard-highlighted list to guard the behavior.
Leaving this thread open in case I have missed a configuration where the highlight is retained across a query change.
The block padding lived on `Combobox.List`, outside the scroll container, so it framed the popup at every scroll position and inset the scrollbar. Drop it, along with the `--available-height` compensation it required. The TanStack demos keep their own spacing inside the virtual content. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rows are painted in a sticky viewport nested inside absolutely positioned content, and padding on the scrollport landed in three different coordinate systems: the absolute content ignored it, the sticky viewport was pinned against the content edge, and the scroll math mixed padded `scrollTop` with unpadded row positions. Padded lists never painted rows in the padding, jumped by the padding at the maximum scroll position, and pushed all of the space below the items when the collection was short enough not to scroll. Measure the block padding and treat it as part of the scroll geometry: the absolute content spans it, the sticky viewport covers the whole scrollport, and row positions convert to scroll offsets. `--total-size` now reports the scrollable content size, so a border-box scrollport sized from it still fits its rows exactly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Opening a virtualized list with an arrow key scanned only the DOM for the first enabled item, so the first unmounted slot was picked as enabled and `isItemDisabled` was ignored. The initial-open scan now consults the consumer predicate alongside the DOM state, keeping the attribute-based skipping that mui#2604 relies on. A disabled `<Combobox.Virtualizer>` stayed registered with the list, which suppressed the DOM `scrollIntoView` that static lists rely on while the virtualizer itself scrolled no rows. The registry entry now carries whether virtualization is enabled, and DOM scrolling is only suppressed while it is. Also list `Combobox.Virtualizer` in the anatomy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c8b6cb57d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…zation # Conflicts: # docs/src/app/(docs)/react/components/combobox/types.md
The adaptive estimate caches were rewritten during render, so a concurrent render that React discards — a transition whose sibling suspends, for example — still cleared the measurements and known row IDs belonging to the tree that stayed committed. The decision is still made during render, because the estimate it invalidates is part of that render's geometry, but it is now applied in a layout effect and the render-visible estimate is derived from the pure decision instead of the ref it is about to clear. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The alignment test slept for exactly `DIRECT_INPUT_WINDOW_MS` after shrinking the rows, so it sampled the geometry at the boundary of the refresh window it was waiting on and failed on slower machines. Shrinking the rows to the estimate makes the virtual total settle at exactly `rows × estimate` once the last measured row is remeasured, which is the point the alignment has to survive. Wait for that instead of for a delay. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
Adds built-in virtualization support to Combobox through a new
<Combobox.Virtualizer>part backed by@mui/x-virtualizer.The adapter preserves the existing Combobox filtering, keyboard navigation, selection, and accessibility behavior while mounting only the visible and overscanned items. The existing
<Combobox.Root virtualized>prop remains available for integrations with other virtualization libraries.What changed
<Combobox.Virtualizer>.@mui/x-virtualizeras a dependency of@base-ui/react.aria-activedescendantand Enter-key selection.isItemDisabledto make disabled state available before an item is mounted, allowing navigation to skip disabled offscreen items.getItemKey, which is required for object values and unknown item types.virtualizedroot prop is not used with the built-in virtualizer.@tanstack/react-virtualintegration:/experiments/combobox/virtualizer.Current limitations
itemsprop on<Combobox.Root>.<Combobox.List>.<Combobox.Item>.Docs
Validation
pnpm test:chromium ComboboxVirtualizer --no-watchpnpm test:jsdom Combobox --no-watchpnpm exec tsgo -b tsconfig.json --forcepnpm prettierpnpm eslintpnpm stylelintpnpm --filter @base-ui/react build