A Searchable, accessible Select / Combobox for React, built with TypeScript, Floating UI, and WAI-ARIA best practices.
This library provides logic and accessibility, not styles.
You bring your own UI.
- ✅ Headless architecture (logic ≠ UI)
- ✅ Single & multi-select
- ✅ Searchable options
- ✅ ARIA compliant combobox / listbox
- ✅ Portal support (optional)
- ✅ Floating positioning with collision handling
- ✅ Z-index safe
- ✅ Mouse + keyboard synced
- ✅ Screen-reader friendly
npm install @hrdi/searchable-selectThis package assumes React 18+ and TypeScript.
<SelectRoot
options={options}
value={value}
onChange={setValue}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectDropdown>
{options.map((opt, i) => (
<SelectOption key={opt.value} option={opt} index={i} />
))}
</SelectDropdown>
</SelectRoot><SelectRoot
options={options}
multiple
searchable
value={value}
onChange={setValue}
>
<SelectTrigger>
<SelectValue placeholder="Select items..." />
</SelectTrigger>
<SelectDropdown portal>
<SelectSearch />
{options.map((opt, i) => (
<SelectOption key={opt.value} option={opt} index={i} />
))}
</SelectDropdown>
</SelectRoot>SelectRoot
export type SelectOptionProps = {
value: string;
label: string;
};
export type UseSelectProps = {
options: SelectOptionProps[];
value?: SelectOptionProps | SelectOptionProps[];
multiple?: boolean;
searchable?: boolean;
filterFn?: (opt: SelectOptionProps, search: string) => boolean;
onChange: (value: SelectOptionProps | SelectOptionProps[]) => void;
};
export type FloatingPlacement = "bottom-start" | "bottom-end";SelectTrigger
Acts as the combobox trigger.
- Handles keyboard interaction
- Holds focus
- Controls open/close state
<SelectTrigger>
<SelectValue />
</SelectTrigger>ARIA:
role="combobox"aria-expandedaria-activedescendant
SelectValue
Renders the selected value(s).
- Single select → label
- Multi select → chips with remove buttons
<SelectValue placeholder="Select..." />SelectDropdown
Renders the floating listbox.
<SelectDropdown portal>
...
</SelectDropdown>Behavior:
- Auto-focuses when dropdown opens
- Filters options via filterFn
ARIA:
- role="textbox"
- aria-autocomplete="list"
SelectOption
Renders an individual option.
Props:
option:SelectOptionindex: number
ARIA:
- role="option"
- aria-selected
This component follows WAI-ARIA Combobox with Listbox pattern.
Implemented:
role="combobox"role="listbox"role="option"aria-expandedaria-selectedaria-activedescendantMeets WCAG 2.1 AA expectations.
- Uses Floating UI
- Auto-flips on viewport edge
- Scroll & resize aware
- Works with any z-index
<SelectDropdown portal />Disable portal if needed:
<SelectDropdown portal={false} />This library is unstyled by design.
Use:
- CSS
- Tailwind
- Styled Components
- Vanilla Extract
- Any design system
Example:
[data-highlighted="true"] {
background: #eef2ff;
}
[aria-selected="true"] {
font-weight: 600;
}Because logic is headless:
- useSelect can be unit tested
- UI primitives can be tested independently
- No DOM-dependent state machines
- ❌ No built-in styles
- ❌ No opinionated UI
- ❌ No framework lock-in
MIT (or internal use)