VirtualSelect
A headless virtualized select component with search, multi-select, grouped options, and async loading. Built on TanStack Virtual.
Basic Usage
10,000 items · Single select · Virtualized
Use the useVirtualSelect hook for full control:
import { useVirtualSelect } from 'virtualized-ui';
const select = useVirtualSelect({
options: countries,
getOptionValue: (c) => c.code,
getOptionLabel: (c) => c.name,
searchable: true,
placeholder: 'Choose a country...',
});Or use the VirtualSelect component:
import { VirtualSelect } from 'virtualized-ui';
<VirtualSelect
options={countries}
getOptionValue={(c) => c.code}
getOptionLabel={(c) => c.name}
searchable
placeholder="Choose a country..."
/>| Option | Type | Default |
|---|---|---|
options | T[] | OptionGroup<T>[] | — |
getOptionValue | (o) => string | required |
getOptionLabel | (o) => string | required |
placeholder | string | — |
disabled | boolean | false |
Features
Explore each feature on its own page:
Searchable
Type-to-filter with custom filter functions
Multi-Select
Select multiple values with tags
Grouped Options
Categorize options with styled group headers
Async Loading
Debounced search with caching and loading states
Sub-Menus
Cascade into nested option hierarchies
Return Values (Hook)
const {
// Virtualizer
virtualizer, // TanStack Virtual instance
virtualItems, // Currently visible virtual items
totalSize, // Total scrollable height (px)
menuRef, // Ref for menu scroll container
measureElement, // Ref callback for dynamic sizing
// State
isOpen, // Whether dropdown is open
searchValue, // Current search text
focusedIndex, // Currently focused option index
selectedValues, // Array of selected value strings
selectedOptions, // Array of selected option objects
isLoading, // Whether async options are loading
flattenedItems, // Flattened items (options + group headers)
// Actions
open, close, toggle,
setSearch, selectValue, deselectValue, toggleValue, clearAll,
setFocusedIndex, openSubMenu, closeSubMenus,
// Event handlers
handleKeyDown, // For the container/trigger
handleMenuKeyDown,// For the menu list
handleSearchInput,// For the search input
// Refs
containerRef, triggerRef, inputRef,
// ARIA helpers
getTriggerProps, getMenuProps, getOptionProps, getInputProps,
} = useVirtualSelect(options);
Controlled vs Uncontrolled
Uncontrolled
State managed internally:
const { selectedValues } = useVirtualSelect({
options: items,
getOptionValue: (o) => o.id,
getOptionLabel: (o) => o.name,
});
// `selectedValues` reflects internal stateControlled
You own the state:
const [value, setValue] = useState<string[]>([]);
useVirtualSelect({
options: items,
getOptionValue: (o) => o.id,
getOptionLabel: (o) => o.name,
value,
onValueChange: setValue,
});