A TypeScript-first searchable dropdown for React that handles the plumbing you don't want to build.
Documentation · NPM · GitHub
Coming from react-select, downshift, or rolling your own combobox, these are the defaults you get instead of add-ons:
- Virtualized by default. Handles 100k+ options without plugins. Powered by Virtuoso.
- Fuzzy search built in. Typo tolerant matching with highlight, backed by match-sorter.
- Single and multi select share one API. Same generic, same props shape, different mode.
- Async search as a first class mode. No separate component to import.
- TypeScript generics end to end. Your option type flows through
options,value,setValue,searchOptionKeys. - No runtime style engine. className slots plus CSS variables. No emotion in your bundle.
- ARIA combobox accessibility. Keyboard nav, focus restore, screen reader announcements.
Full comparisons: vs react-select (component camp) · vs Downshift (headless camp)
Follows the ARIA combobox pattern out of the box.
role="combobox"on the input witharia-expanded,aria-controls,aria-autocomplete="list".role="listbox"on the option list witharia-activedescendanttracking the highlighted item.- Keyboard: arrow keys,
Enterto select,Escapeto close and restore focus to the trigger,Tabto leave without selecting. - Multi select adds
Backspaceto remove the last chip when the input is empty. - Focus is trapped inside the dropdown while open and returned to the trigger on close.
- Screen reader announcements for filtered result counts as the user types.
npm install @luciodale/react-searchable-dropdownThe minimum to get a searchable, virtualized dropdown with keyboard navigation.
// city-picker.tsx
import { useState } from "react";
import { SearchableDropdown } from "@luciodale/react-searchable-dropdown";
import "@luciodale/react-searchable-dropdown/dist/single-style.css";
const cities = ["London", "Paris", "Berlin", "Madrid", "Rome", "Amsterdam"];
function CityPicker() {
const [city, setCity] = useState<string | undefined>(undefined);
return (
<SearchableDropdown
options={cities}
value={city}
setValue={setCity}
placeholder="Pick a city"
/>
);
}Pass an array of strings, a value, and a setter. You get fuzzy search, virtual scrolling, portal positioning, and full keyboard navigation for free.
- Virtualized rendering — handles hundreds of thousands of options without breaking a sweat. Only visible items are rendered, powered by Virtuoso.
- Portal positioning — works inside overflow:hidden containers, modals, and scrollable areas. Floating UI handles the positioning so the dropdown never clips.
- Fuzzy search filtering — configurable match strategies including exact, starts-with, contains, and acronym. Powered by match-sorter with optional debouncing for large lists.
- Full keyboard navigation — arrow keys, enter, escape, tab. Multi-select adds backspace to remove the last chip.
- Single and multi-select — two components with the same API surface. Multi-select manages chips, clear-all, and automatic deduplication.
- Grouped options — dynamic category headers that recalculate as the user searches. Empty groups disappear automatically.
- TypeScript generics —
SearchableDropdown<T>propagates your option type acrosssearchOptionKeys,setValue, andhandleGroups. Discriminated unions enforce that object options require search keys while string options don't. - Full styling control — CSS class props, CSS variables, or skip the defaults entirely and bring your own. Custom classes replace defaults, no specificity battles.
Selected items appear as removable chips. The dropdown automatically filters out already-selected options.
// skill-picker.tsx
import { useState } from "react";
import { SearchableDropdownMulti } from "@luciodale/react-searchable-dropdown";
import "@luciodale/react-searchable-dropdown/dist/multi-style.css";
const skills = ["TypeScript", "React", "Node.js", "Python", "Go", "Rust"];
function SkillPicker() {
const [selected, setSelected] = useState<string[] | undefined>(undefined);
return (
<SearchableDropdownMulti
options={skills}
values={selected}
setValues={setSelected}
deleteLastChipOnBackspace
placeholder="Select skills..."
/>
);
}Backspace with an empty search input removes the last chip. A clear-all button appears when there are selected values.
When your options are objects, searchOptionKeys tells the dropdown which fields to search against. TypeScript enforces that only keys from your option type are accepted.
// user-picker.tsx
import { useState } from "react";
import { SearchableDropdown } from "@luciodale/react-searchable-dropdown";
import "@luciodale/react-searchable-dropdown/dist/single-style.css";
type User = {
label: string;
value: string;
department: string;
};
const users: User[] = [
{ label: "Alice", value: "alice", department: "Engineering" },
{ label: "Bob", value: "bob", department: "Design" },
{ label: "Carol", value: "carol", department: "Engineering" },
];
function UserPicker() {
const [user, setUser] = useState<User | undefined>(undefined);
return (
<SearchableDropdown
options={users}
value={user}
setValue={setUser}
searchOptionKeys={["label", "department"]}
placeholder="Search by name or department"
/>
);
}Rename department to team in the User type and TypeScript flags searchOptionKeys immediately. The generics flow through so nothing gets out of sync.
Full documentation, configuration reference, and live demos at koolcodez.com/projects/react-searchable-dropdown.
MIT