SearchSelect
Picks one of many — a studio among hundreds — where a Select's list would run long. A field like Select's that opens a search over the options, typed into at once; the search is the house Input. Over the vendored Base UI combobox. On a phone it opens as a bottom sheet.
Playground
import { SearchSelect } from "@/components/shared/search-select";
<SearchSelect
items={studios}
value={slug}
onValueChange={setSlug}
searchPlaceholder="Search studios"
empty="No studio matches"
aria-label="Studio"
/>Finding an option
Every word typed must be somewhere in the option's label or its keywords, in any order, any case, accents or not: “studio kr” finds Štúdio Krása, and so does its slug.
import { SearchSelect } from "@/components/shared/search-select";
const studios = [
{ value: "", label: "All studios" },
{ value: "studio-krasa", label: "Štúdio Krása", keywords: "studio-krasa" },
…
];
<SearchSelect
items={studios}
value={slug}
onValueChange={setSlug}
searchPlaceholder="Search studios"
empty="No studio matches"
aria-label="Studio"
/>A figure on each row
A row can carry one short figure at its end — what each studio spent — small and quiet, in tabular numerals so they line up.
import { SearchSelect } from "@/components/shared/search-select";
{ value: "studio-krasa", label: "Štúdio Krása", detail: "$4.12" }Beside a Select
The same field as Select — its surface, edge, heights, chevron — so the two read as one kind in a row. Only the open list differs: a search over it.
<Select items={durations} defaultValue="30 min">…</Select>
<SearchSelect items={studios} value={slug} onValueChange={setSlug} />On a phone, a sheet
Under sm the search opens as a bottom sheet (Sheet), as a Select's list does: the full width, the search at the top with the keyboard up, standing on it, rows 44px tall and the list scrolling in what's left. Narrow this window to see it.
API
| Prop | Type | Default | Description |
|---|---|---|---|
| items | SearchOption[] | — | The options: value, label, and optionally detail and keywords. |
| value | string | — | The chosen option's value. One not in items shows the placeholder. |
| onValueChange | (value: string) => void | — | Called with the picked option's value. |
| size | "sm" | "md" | "lg" | "md" | 32, 36 or 40px: the heights of Button and Select. |
| placeholder | string | "Choose" | In the field with nothing chosen. |
| searchPlaceholder | string | "Search" | In the search box, and its accessible name. |
| empty | string | "Nothing matches" | In the list when nothing matches. |
| aria-label | string | — | Names the field when no label sits beside it. |
| className | string | — | Layout only — a width. |
Rules
- Past a dozen options, or a list that grows with use, use SearchSelect; under that, Select.
- Put a way out first when there is one — “All studios” — so it’s never a search away.
- A figure is one short value, the same kind on every row; never a sentence.