Fields and selection
Select Search
A searchable Base UI select for larger option sets.
Install
npx shadcn@latest add https://4af.selfsimilar.dev/r/select-search.jsonUsage
import { SearchSelect, SearchSelectInput, SearchSelectContent, SearchSelectItem } from "@/components/ui/select-search";
// Focusing dims the selection; clicking a row commits it and unfocuses the field.
<SearchSelect items={items} defaultValue={items[0]}>
<SearchSelectInput aria-label="Choose an option" placeholder="Search…" />
<SearchSelectContent>
{(item) => <SearchSelectItem key={item.value} value={item}>{item.label}</SearchSelectItem>}
</SearchSelectContent>
</SearchSelect>API
Main exports: SearchSelect, SearchSelectInput, SearchSelectContent, and item parts
An editable Combobox-style trigger with live filtering, single or checkbox-based multi-selection, icons, and full keyboard and ARIA behaviour.
Searching and cancelling
In single-select mode:
- The selected value shows in normal text, and dims while the field is focused. Typing starts a new query over it.
- Opening highlights the current selection; typing highlights the top match.
- Choosing a row commits it. Clicking a row also unfocuses the field; selecting with the keyboard keeps focus.
- Escape discards the query and keeps focus. Clicking outside, or deleting the query, keeps the current selection.
- When the selection has an icon, focusing the field swaps it for a dimmed search icon until the field loses focus.
In multi-select mode the panel stays open for more choices.
inputValue / defaultInputValue and onInputValueChange hold the search query, separately
from value / defaultValue and onValueChange. itemToStringLabel also supplies the selected
label for custom item shapes. Multi-select shows a count summary and checkboxes.
Sizing
size="sm" is the 30px compact control and size="md" is the 34px form-row control.
The input trigger and its option rows share the selected size, and the input uses the matching
button radius: 6px for sm and 8px for md.
Item data
An item is its own value (<SearchSelectItem value={item}>), so the icon goes on the item
object. Do this when a preselected value must show its icon before the panel has opened:
const fruits = [{ value: "apple", label: "Apple", icon: <Apple /> }];
<SearchSelect items={fruits} defaultValue={fruits[0]}>An icon passed only to SearchSelectItem is available after the panel mounts. Resolution order
is the mounted item, then iconForValue, then the value's own icon.
Highlight implementation
Opening activates the current selection, or the most recently selected value in multi-select; without a selection it activates the first enabled row. A query activates the top match, and leaving the panel keeps the active row.
The Base UI integration uses autoHighlight="always" only when nothing is selected: that mode
pins the first row over an existing selection. With a selection, plain autoHighlight restores
it. Keep keepHighlight enabled so pointer exit does not clear the mark and reset Enter's target.