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.json

Usage

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.