Combobox
A text field with a list under it: type to filter, pick one value or many.
reacttypescript
- Type to filter; the list opens under the field.
- Arrows move the active option, Enter picks it, Escape closes.
- In multiple mode values become tags; Backspace removes the last.
Field marks
- Import
import { Combobox } from "cennge-ui";- Size
- 6.8 KB gzip for this import, its dependencies aside
- Pattern
- WAI-ARIA combobox with a listbox popup
- Keyboard
- Arrow up Arrow down HomeEndEnter Esc
- State
- Controlled with
valueandonValueChange, or uncontrolled - SSR
- Rendered on the server in the Next.js check; hydrates without a warning
- Virtualizes
- Past 100 options, by itself
- Tests
- Unit tests; end-to-end in Chromium, Firefox and WebKit, on desktop and phone viewports: combobox, dialog-scroll
- Source
- src/components/combobox
When to use#
Reach for a Combobox when the list is long enough that people would rather type than scroll. It filters as you type, virtualizes itself past 100 options, and with isClearable one click returns it to the placeholder.
Similar#
- Select is a fixed short list with nothing to type.
- TagsInput makes tags from any free text; a Combobox with
selectionMode="multiple"only takes tags from its list.
Pick one repository#
value is the chosen item's value, or null when nothing is chosen. isClearable adds a button that sets it back to null.
basic.tsx
import { useState } from "react";
import { Combobox } from "cennge-ui";
const repositories = [
{ value: "cennge-ui", label: "cennge-ui", description: "React UI library" },
{ value: "ai-niche-scout", label: "ai-niche-scout" },
{ value: "payload-cache", label: "payload-cache" },
];
export default function RepositoryPicker() {
const [repository, setRepository] = useState<string | null>(null);
return (
<Combobox
label="Repository"
placeholder="Pick a repository"
items={repositories}
value={repository}
onValueChange={setRepository}
isClearable
className="w-full max-w-80"
/>
);
}A thousand options#
Past 100 items the list renders only the rows in view, so typing stays instant. The active option is always in the DOM, so screen readers follow it.
long-list.tsx
import { Combobox } from "cennge-ui";
// 1,000 options: past 100 the list renders only the rows in view.
const releases = Array.from({ length: 1000 }, (_, index) => {
const version = `0.${Math.floor(index / 100)}.${index % 100}`;
return { value: version, label: `cennge-ui@${version}` };
});
export default function ReleasePicker() {
return <Combobox label="Release" placeholder="Type a version, e.g. 0.3" items={releases} className="w-full max-w-80" />;
}