Skip to content

Conversation

zachstence
Copy link
Contributor

I came across this bug while trying to implement virtualization with the shadcn-svelte Select component.

The flow looks like this:

  • When the user scrolls the list, the virtualization adds/removes Items depending on the direction of the scroll
  • When Items are added to the Select setInitialHighlightedNode is called which then highlights and scrolls to the first candidate (non-disabled) Item.
  • Because the virtualized list renders some Items above the list out of view, we scroll to an out of view Item which interrupts the user's scrolling and allows only one Item to scroll at a time.

My fix highlights and scrolls to the first candidate Item that is within the viewport. This way, we aren't snapping back to an item that has already been scrolled past.

The only part I'm unsure about is the way I got access to the viewportNode in the SelectBaseRootState class:


Reproduction code (uses virtua for virtualization)

<script lang="ts">
	import * as Select from '$lib/components/ui/select';
	import { Virtualizer, type VirtualizerHandle } from 'virtua/svelte';

	const data = Array.from({ length: 100 }).map((_, i) => i);

	let value: string | undefined = $state();
	let virtualizer: VirtualizerHandle = $state(null!);
</script>

<Select.Root type="single" bind:value>
	<Select.Trigger>{value || 'Select'}</Select.Trigger>
	<Select.Content class="max-h-[300px]">
		<Virtualizer bind:this={virtualizer} {data} getKey={(_, i) => i}>
			{#snippet children(item)}
				<Select.Item value={item.toString()}>Option {item}</Select.Item>
			{/snippet}
		</Virtualizer>
	</Select.Content>
</Select.Root>

Before fix:

firefox_Ib3Rg947sP.mp4

After fix:

firefox_PRuUU8pyyJ.mp4

Copy link

changeset-bot bot commented Oct 15, 2025

🦋 Changeset detected

Latest commit: 5464919

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
bits-ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

github-actions bot commented Oct 15, 2025

built with Refined Cloudflare Pages Action

⚡ Cloudflare Pages Deployment

Name Status Preview Last Commit
bits-ui ✅ Ready (View Log) Visit Preview 5464919

@zachstence
Copy link
Contributor Author

On second thought - maybe just a flag to disable the scrollIntoView would be preferred? Then it would behave similarly to a non-virtualized Select - the highlighted item just scrolls out of view.

@huntabyte
Copy link
Owner

I think we should be able to handle this with a simpler solution - getBoundingClientRect is a bit of an expensive call.

The issue really boils down to better support for items added after the content is opened. Right now we basically register the items wehn they mount and call setInitialHighlightedNode because we don't know in advance which node is going to be highlighted/rendered first.

Though it is strange that it is still running the scroll because we have a conditional to return early if a highlighted node already exists.

Will look into this, thanks for kicking it off!

@huntabyte
Copy link
Owner

Just to ensure I'm debugging properly, would you remind reproducing with this sandbox here:
CleanShot 2025-10-15 at 21 24 30@2x

@zachstence
Copy link
Contributor Author

zachstence commented Oct 16, 2025

Here's the repro in Stackblitz: https://stackblitz.com/edit/bits-ui-1830-repro

The bug looks a little different here - I can scroll two items at a time if I just scroll one notch on my mouse, and if I scroll 2 or 3 notches I actually get set backwards when bits-ui highlights the first item. The exact behavior depend on the overscan given to virtua and the scroll speed of your mouse.

In this video I am only ever scrolling downwards

chrome_D57jxKAgdb.mp4

Thanks for looking into this! Let me know if I can help out further!

@huntabyte
Copy link
Owner

I'm trying to figure out why this is happening. We have a guard to check if there is already a highlighted item then we don't do anything:
CleanShot 2025-10-16 at 16 07 50@2x

@huntabyte
Copy link
Owner

Is it because with virtua it removes the nodes from the DOM when they are outside of the viewport?

@huntabyte
Copy link
Owner

huntabyte commented Oct 16, 2025

Does the issue persist in this repro here for you? Sorry I'm having a hard time reproducing (probably my scroll speed is out of wack or something): https://stackblitz.com/edit/bits-ui-1830-repro-yf7dpvnh this stackblitz is running the pkg pr new of the PR I have open.

Just realized that preview now doesn't continuously scroll when hovering the scroll down button 🤦‍♂️

@huntabyte
Copy link
Owner

Alright we'll go with this solution as I know virtualization is important and this is definitely something we want to support, thanks for taking the time to dig into this and coming up with a solution!

Copy link
Owner

@huntabyte huntabyte left a comment

Choose a reason for hiding this comment

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

Thank you!

@huntabyte huntabyte merged commit 630e7b6 into huntabyte:main Oct 16, 2025
6 checks passed
@github-actions github-actions bot mentioned this pull request Oct 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants