Select
Searchable, async-capable, optionally virtualized select with custom item renderers, RTL, locale-aware sort, and OptGroup support.
At a glance
Two operating modes:
- Static — pass
options. The picker filters in-memory by substring match onlabel(override viafilterOption). - Async — pass
loadOptions(params). Called on search-input change (debounced ~150 ms) and when the list scrolls near the bottom for the next page. The component manages the loaded list and pagination cursor itself, so consumers don't have to.
Other capabilities:
- Searchable — auto-on for static lists ≥ 10 items and always on for async; override with
searchable. - Virtualized list — auto-on for lists > 100 items; only the visible window of options is rendered.
- OptGroup support — items with the same
groupfield cluster under a group header. - Locale-aware sort — pass
locale="de"(or any BCP 47) and the picker re-sorts viaIntl.Collator. Switch the locale and the list re-orders alphabetically for the new language. - Custom item renderer — pass
renderOptionfor arbitrary content per row. - RTL — pass
dir="rtl"; popup alignment + text direction flip. - Keyboard nav — Arrow Down/Up move highlight, Enter selects, Escape closes, Tab closes.
Static — with groups
Items with the same group field cluster under a header. Searchable auto-on once the list reaches 10 items.
Multi-select
Pass multiple to switch the value / onChange types to arrays. The trigger renders a chip per selected option (collapsing to "N selected" past maxChips, default 3); the popup shows a checkbox-style indicator next to each option, stays open between picks, and exposes a "Clear all" affordance plus aria-multiselectable="true". Optional maxSelected caps the count — extra picks are silently ignored.
Capped selection
Cap the number of selectable items with maxSelected — extra picks are ignored silently. Pair with a smaller maxChips so the trigger stays compact while the cap is approached. The example below caps at 3 and collapses to "N selected" past 2 chips.
Async + paginated
loadOptions is called on every search change (debounced ~150ms) and on scroll near the bottom of the list. The component manages the loaded array and pagination cursor; consumers only return { items, total? }.
Virtualized
Pass a thousand items in. Only the visible window is rendered (auto-on once the option count crosses 100).
Custom item renderer
renderOption(option, { selected, active }) returns the JSX for each row. The picker still owns selection state, keyboard nav, and group headers — the custom renderer only owns the row's content.
Locale-aware sort
locale="de" (or any BCP 47) re-sorts the list via Intl.Collator. Switch the locale and the order updates so the alphabet matches the language a reader is in. Pair with sortByLocale={false} to keep your preferred order while still benefiting from a locale-tagged accessibility tree.
Use with Field
For labelled selects with description, error, and a11y wiring, wrap in <Field>:
With validation error:
See Field for the full API. For custom layouts (e.g., a control + button row), use the compound API.
value and defaultValue
Controlled via value (paired with onChange); uncontrolled via
defaultValue. Single-mode types are string; multi-mode (multiple)
types are string[]. Mixing the two prefers controlled.
placeholder, searchPlaceholder, noOptionsMessage, loadingMessage
Each of the user-visible labels is overridable. Use them to localize or to write copy that matches your product voice.
| Prop | Default | Where it shows |
|---|---|---|
placeholder | "Select…" | Trigger when nothing is selected |
searchPlaceholder | "Search…" | Search input when searchable is on |
noOptionsMessage | "No options" | Popup body when the filtered list is empty |
loadingMessage | "Loading…" | Popup body while async results are pending |
disabled
Greys the trigger, blocks pointer + keyboard interaction, and
forwards aria-disabled. Individual options can also be disabled via
disabled: true on the option.
dir
Right-to-left layout. Flips the popup alignment, text direction, and icon sides so the field reads naturally in Arabic / Hebrew / RTL locales.
aria-label
Accessibility label for the trigger when there's no visible label
elsewhere. Forwarded to the combobox role.
Performance — virtualized, itemHeight, maxMenuHeight, pageSize
Knobs for tuning the popup with large datasets:
virtualized— render only the visible window of options. Auto-on for lists > 100 items; passfalseto force off,trueto force on.itemHeight— fixed pixel height per row. Default 36. Required input for the virtualization math; bump it if you render taller custom rows viarenderOption.maxMenuHeight— popup height in px before it scrolls. Default 320.pageSize— async-only; how many items to fetch perloadOptionscall. Default 50.
sortByLocale
When locale is set the picker auto-sorts via Intl.Collator. Pass
sortByLocale={false} to keep your incoming option order while still
exposing the locale to assistive tech and to per-row formatting.
Field integration and accessibility props
Wrap Select in <Field> for a label, description, and error message with
automatic ARIA wiring. The following attributes are injected onto the trigger
automatically by Field.Control:
id— matches thehtmlForof the label generated byField.aria-labelledby— references theField.Labelelement's ID.aria-describedby— referencesField.Descriptionand/orField.Errorwhen present.aria-invalid— set when the enclosingFieldhas a non-nullerror.aria-required— set whenFieldhasrequired={true}.
You can also pass any of these props explicitly when using Select without a
Field wrapper.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
aria-describedby | string | — | — |
aria-invalid | boolean | — | — |
aria-label | string | — | — |
aria-labelledby | string | — | — |
aria-required | boolean | — | — |
className | string | — | — |
defaultValue | string | readonly string[] | — | Uncontrolled initial value. Uncontrolled initial values. |
dir | enum | — | RTL flips the popup alignment + text direction. |
disabled | boolean | — | Disable interaction. |
filterOption | (option: SelectOption<T>, search: string) => boolean | — | Override the default substring filter for static options. |
id | string | — | — |
itemHeight | number | — | Pixel height of a single item — required for virtualization math. @defaultValue 36 |
loadingMessage | string | — | Message shown while async results are loading. |
loadOptions | (params: LoadOptionsParams) => Promise<LoadOptionsResult<T>> | — | Async loader. Called with `{ search, offset, limit }` whenever the search input changes (debounced) or the user scrolls near the end of the loaded list. Return more items + an optional total to stop the pagination loop early. |
locale | string | — | BCP 47 locale — drives `Intl.Collator` sorting of options when set. Re-sorts on language switch so a German list reads alphabetically in German vs the same list in English. |
maxChips | number | — | Max chips to render in the trigger before collapsing to "N selected". @defaultValue 3 |
maxMenuHeight | number | — | Max popup height in px. @defaultValue 320 |
maxSelected | number | — | Hard cap on selected count — extra picks are ignored. |
multiple | boolean | false | Single-select mode (default — omit or pass `false`). Multi-select mode — value/onChange become array-typed. |
name | string | — | — |
noOptionsMessage | string | — | Message shown in the popup when there are no matching options. |
onChange | ((value: string, option: SelectOption<T>) => void) | ((values: readonly string[], options: readonly SelectOption<T>[]) => void) | — | Fires when the user picks an option. Fires when the selection changes. Receives the full new array of values + their resolved options. |
options | readonly SelectOption<T>[] | — | Static options. Mutually exclusive with `loadOptions`. |
pageSize | number | — | Page size for `loadOptions`. @defaultValue 50 |
placeholder | string | — | Trigger placeholder when no value is selected. |
renderOption | (option: SelectOption<T>, info: SelectRenderOptionInfo) => ReactNode | — | Custom item renderer. Called per option in the list. |
searchable | boolean | — | Show a search input above the list. @defaultValue auto-on for static options >= 10 items, always on for loadOptions |
searchPlaceholder | string | — | Placeholder for the search input. |
sortByLocale | boolean | — | When `locale` is set, sort options alphabetically. @defaultValue true |
testID | string | — | — |
value | string | readonly string[] | — | Controlled value. Controlled values. |
virtualized | boolean | — | Virtualize the list — only DOM-render the visible window of items. Auto-on when the list has more than 100 items. |