Select
Purpose-built select field built on top of Picker for standard choose-from-list form inputs.
Overview
Select is the standard choose-from-list field built on top of the Picker primitive.
It also composes the shared field-shell primitives:
Labelfor the field labelFieldErrorfor inline validation feedback
It wraps the lower-level picker composition into a form-friendly field with:
- optional
label - optional
labelIcon - built-in error display
- placeholder support
- optional in-menu search
- optional multi-select
- empty states
If you need a normal dropdown field, use Select before reaching for raw Picker.
Layering
| Component | Role |
|---|---|
Picker | Base primitive |
Label | Shared label primitive used by field components |
FieldError | Shared inline validation-message primitive |
Select | Standard field wrapper for choose-from-list selection |
AutocompleteSelect | Search-first field for type-to-filter and async option discovery |
Basic Usage
<Select
label="Time Notation"
selected={preferences.timeNotation}
onSelect={handleUpdateTimeNotation}
options={timeNotationOptions}
/>This is the common settings and form-field pattern used in places like AccountPreferences.
Inline Workflow Select
Select also works well when the field is embedded inside a denser workflow UI instead of a traditional form layout.
<Select
placement="bottom-end"
options={[
{ value: 'read_only', label: 'Read Only' },
{ value: 'chat', label: 'Chat' },
{ value: 'admin', label: 'Admin' },
]}
selected={field.value || 'chat'}
onSelect={field.onChange}
/>This is the pattern used in ChatOverview for participant permissions.
Multi-Select
<Select
label="States"
multiSelect
options={stateOptions}
selected={selectedStates}
onSelect={setSelectedStates}
/>Multi-select display behavior:
- no selection: placeholder
- one selection: selected option label or custom render
- multiple selections:
"{count} selected"
With Custom Option Content
Each option can include an icon, avatar, or render() function.
<Select
selected={field.value}
onSelect={field.onChange}
onBlur={field.onBlur}
error={errors?.status?.message}
label="Location Status*"
labelIcon="CircleHalfFill"
size="sm"
options={LOCATION_STATUS_OPTIONS.map((option) => ({
value: option.value,
label: option.label,
render: () => (
<Badge
color={option.color}
label={option.label}
Icon={getLocationStatusIconName(option.value)}
/>
),
}))}
/>This mirrors the status field in CreateLocationModal, where the dropdown rows render badges instead of plain text.
Props
Shared Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | { label, value, icon?, render?, avatar?, disabled? }[] | — | Required. Available options. |
label | string | — | Field label shown above the trigger. |
labelIcon | IconName | — | Small icon next to the label. |
id | string | — | Trigger/input id. |
required | boolean | false | Adds required asterisk in the label. |
icon | IconName | — | Default left icon on the trigger. |
placeholder | string | 'Select' | Placeholder when nothing is selected. |
searchable | boolean | false | Adds an in-menu search input. |
onChangeSearchText | (value: string) => void | — | Called when the internal search text changes. |
error | string | null | — | Shows field error below the control. |
onBlur | () => void | — | Blur handler for form integration. |
size | 'sm' | 'md' | 'md' | Field size. |
onListEndReached | () => void | — | Called when the bottom sentinel becomes visible. |
disabled | boolean | false | Disables interaction. |
className | string | — | Wrapper class name. |
placement | Placement | 'bottom-start' | Dropdown placement. |
emptyStateTitle | string | — | Empty state title. |
emptyStateDescription | string | — | Empty state description. |
emptyStateAction | { label, onClick } | — | Optional empty state action button. |
Field Shell Props
These props are handled through Label and FieldError inside Select.
| Prop | Primitive used | Description |
|---|---|---|
label | Label | Renders the field label above the trigger. |
labelIcon | Label | Adds a small icon inline with the label text. |
required | Label | Adds the required asterisk in the label row. |
error | FieldError | Renders inline validation feedback below the control. |
Single Select Props
| Prop | Type | Description |
|---|---|---|
selected | T | null | Current selected value. |
onSelect | (value: T) => void | Required. Called when a value is selected. |
multiSelect | false | Single-select mode. |
Multi-Select Props
| Prop | Type | Description |
|---|---|---|
selected | T[] | Current selected values. |
onSelect | (value: T[]) => void | Required. Called with the updated set of selected values. |
multiSelect | true | Enables multi-select behavior. |
How Select Uses Picker
Internally, Select is a purpose-built composition of:
PickerrootPicker.Buttonas the triggerPicker.Contentas the dropdown panelPicker.Content.Searchwhensearchableis enabledPicker.Itemfor option rowsPicker.Emptyfor empty state displayLabelfor the field labelFieldErrorfor the validation message
That means Select inherits the picker value model and selection semantics, but presents them as a standard field component.
When To Use Select
Use Select when:
- the user is choosing from a known list
- the list fits a standard dropdown model
- in-menu search is enough
- the control should behave like a normal form field
Prefer AutocompleteSelect when:
- typing is the main interaction
- options are large enough that search-first interaction matters
- grouping and search-first exploration matter more than dropdown simplicity
- selected options can still be kept present in the available option set
Prefer raw Picker when:
- you need a custom trigger
- the control is not really a form field
- you need a custom popup layout
Brand Dashboard Usage
Representative usages:
| Pattern | Path | Notes |
|---|---|---|
| Preferences field | fsai/apps/brand-dashboard/src/pages/Account/AccountPreferences/AccountPreferences.tsx | Simple labeled settings-field usage. |
| Inline workflow selector | fsai/apps/brand-dashboard/src/pages/Home/ChatOverview/ChatOverview.components.tsx | Compact permission select inside a participant row. |
| Custom rendered options | fsai/apps/brand-dashboard/src/modules/locations/components/CreateLocationModal/CreateLocationModal.tsx | Form select with badge-rendered options. |
| Small action select | fsai/apps/brand-dashboard/src/pages/EmailBuilder/EmailEditorSidebar/EmailEditorSidebar.components.tsx | Small size="sm" control used to add a department. |
Important Cautions
searchableis in-memory label filtering. It is not a full autocomplete pattern.- Object values must expose a stable
id. Selectis a better default than rawPicker, but not a replacement forAutocompleteSelect.- If you need remote filtering or a search-first interaction, choose based on UX first, not because data happens to be async.
Guidelines
- Do use
Selectas the default dropdown field in forms - Do use
multiSelectonly when the UI truly supports multi-value selection well - Do use
searchablefor medium-size in-memory option sets - Don't use
Selectfor async typeahead experiences - Don't use raw
Pickerfor ordinary form dropdowns unless you need custom composition