Select
A dropdown for choosing from a predefined list. It uses Base UI Select by
default and Base UI Combobox when searchable is enabled. Options can be flat
or grouped in every mode.
import { Select } from '@jbpark/ui-kit';
<Select
placeholder="Choose a framework"
options={[
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
]}
onChange={value => console.log(value)}
/>;
For a long list of predefined options, add searchable to show a search field
inside the popup while keeping the same closed trigger. The label is required
in this mode to give the trigger and popup accessible names. Searchable option
and group labels must be strings so they can be filtered.
<Select
searchable
label="Choose a framework"
placeholder="Select a framework"
searchPlaceholder="Search frameworks"
options={[
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
]}
onChange={value => console.log(value)}
/>
Use the default Select for short lists. Use Input.Search when users should enter free-form text rather than choose a predefined value.
For multiple selections, combine searchable with multiple. The input and
selected chips remain visible while filtering, and each chip has a remove
button. Unlike the single searchable mode, the input sits alongside the chips
outside the popup so selected values remain visible. The selected value is an
array of option IDs; clearing it returns [].
<Select
searchable
multiple
label="Choose frameworks"
options={[
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte' },
]}
defaultValue={['react']}
onChange={values => console.log(values)}
/>
Props
| Prop | Type | Default |
|---|---|---|
options | (Option | OptionGroup)[] | - |
placeholder | string | - |
onChange | string / string | null / string[] callback, depending on mode | - |
className | string | - |
searchable | boolean | false |
multiple | true (with searchable) | false |
label | string (searchable modes) | - |
searchPlaceholder | string (searchable modes) | Search... |
emptyText | React.ReactNode (searchable modes) | No results found. |
allowClear | boolean (searchable modes) | true |
classNames | { input?, content?, list?, item?, empty?, chips?, chip? } | - |
chipDescription | string (multiple mode) | Keyboard removal hint |
inputDescription | (selectedCount: number) => string (multiple mode) | Keyboard chip navigation hint |
interface Option {
label: React.ReactNode;
value: string;
disabled?: boolean;
}
interface OptionGroup {
label: React.ReactNode;
options: Option[];
}
An OptionGroup entry (an options array instead of value) renders as a
labeled group. Select also accepts the rest of Base UI's Select root
props (e.g. value, defaultValue, disabled, name, required) except
onValueChange, which is renamed to onChange above. In searchable mode,
onChange receives string | null, because clearing a selection emits null.
It accepts the corresponding Base UI Combobox root props instead of Select root
props. In multiple mode, onChange receives string[]; chips can be removed
with their buttons or by focusing a chip and pressing Backspace or Delete.
Labelling
id reaches the element that receives focus in every mode: the trigger, or
the search input in multiple mode. aria-labelledby, aria-describedby and
aria-invalid go to the same element. In the default mode, pair id with an
external <label htmlFor> (or pass aria-label). In searchable modes, label
sets aria-label, so name the control from a visible label with
aria-labelledby, which takes precedence.