Select
A high-quality, unstyled React select component for choosing a predefined value in a dropdown menu.
A common form component for choosing a predefined value in a dropdown menu.
Usage guidelines
- Prefer Combobox for large lists: Select is not filterable, aside from basic keyboard typeahead functionality to find items by focusing and highlighting them. Prefer Combobox instead of Select when the number of items is sufficiently large to warrant filtering.
- Special positioning behavior: The select popup by default overlaps its trigger so the selected item's text is aligned with the trigger's value text. This behavior can be disabled or customized.
- Form controls must have an accessible name: It can be created using the
Fieldcomponent. See Labeling a select and the forms guide.
Anatomy
Import the component and assemble its parts:
import { Select } from '@base-ui/react/select';
<Select.Root>
<Select.Trigger>
<Select.Value />
<Select.Icon />
</Select.Trigger>
<Select.Portal>
<Select.Backdrop />
<Select.Positioner>
<Select.ScrollUpArrow />
<Select.Popup>
<Select.Arrow />
<Select.List>
<Select.Item>
<Select.ItemText />
<Select.ItemIndicator />
</Select.Item>
<Select.Separator />
<Select.Group>
<Select.GroupLabel />
</Select.Group>
</Select.List>
</Select.Popup>
<Select.ScrollDownArrow />
</Select.Positioner>
</Select.Portal>
</Select.Root>;Positioning
<Select.Positioner> has a special prop called alignItemWithTrigger which causes the positioning to act differently by default from other Positioner components.
The prop makes the select popup overlap the trigger so the selected item's text is aligned with the trigger's value text.
For styling, data-side is "none" on the .Popup and .Positioner parts when the mode is active.
To prevent the select popup from overlapping its trigger, set the alignItemWithTrigger prop to false.
When set to true (its default) there are a few important points to note about its behavior:
- Interaction type dependent: For UX reasons, the
alignItemWithTriggerpositioning mode is disabled if touch was the pointer type used to open the popup. - Viewport space dependent: There must be enough space in the viewport to align the selected item's text with the trigger's value text without causing the popup to be too vertically small - otherwise, it falls back to the default positioning mode.
This can be customized by setting
min-heighton the<Select.Positioner>element; a smaller value will fallback less often. Additionally, the trigger must be at least 20px from the edges of the top and bottom of the viewport, or it will also fall back. - Other positioning props are ignored: Props like
sideoralignhave no effect unless the prop is set tofalseor when in fallback mode.
Examples
Typed wrapper component
The following example shows a typed wrapper around the Select component with correct type inference and type safety:
import * as React from 'react';
import { Select } from '@base-ui/react/select';
export function MySelect<Value, Multiple extends boolean | undefined = false>(
props: Select.Root.Props<Value, Multiple>,
): React.JSX.Element {
return <Select.Root {...props}>{/* ... */}</Select.Root>;
}Formatting the value
By default, the <Select.Value> component renders the raw value.
Passing the items prop to <Select.Root> instead renders the matching label for the rendered value:
const items = [
{ value: null, label: 'Select theme' },
{ value: 'system', label: 'System default' },
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
];
<Select.Root items={items}>
<Select.Value />
</Select.Root>;A function can also be passed as the children prop of <Select.Value> to render a formatted value:
const items = {
monospace: 'Monospace',
serif: 'Serif',
'san-serif': 'Sans-serif',
};
<Select.Value>
{(value: keyof typeof items) => (
<span style={{ fontFamily: value }}>
{items[value]}
</span>
)}
</Select.Value>;To avoid lookup, object values for each item can also be used.
Labeling a select
Use the Field component to provide a visible label for the select trigger:
<Field.Root>
<Field.Label nativeLabel={false} render={<div />}>
Theme
</Field.Label>
<Select.Root>{/* ... */}</Select.Root>
</Field.Root>Replace the rendered <label> element with a <div> element and add nativeLabel={false} so it does not inherit native label behaviors. This ensures clicking on the label will focus the select trigger without opening the associated popup to match native <select> behavior, and prevents CSS :hover from activating on the trigger when hovering over the label.
Placeholder values
To show a placeholder value, use the placeholder prop on <Select.Value>:
const items = [
{ value: 'system', label: 'System default' },
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
];
<Select.Root items={items}>
<Select.Value placeholder="Select theme" />
</Select.Root>;With placeholders, users cannot clear selected values using the select itself. If the select value should be clearable from the popup (instead of an external "reset" button), use a null item rendered in the list itself:
const items = [
{ value: null, label: 'Select theme' },
{ value: 'system', label: 'System default' },
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
];
<Select.Root items={items}>
<Select.Value />
</Select.Root>;Multiple selection
Add the multiple prop to the <Select.Root> component to allow multiple selections.
Object values
Select items can use objects as values instead of primitives.
This lets you access the full object in custom render functions, and can avoid needing to specify items for lookup.
Grouped
Organize related options with <Select.Group> and <Select.GroupLabel> to add section headings inside the popup.
Groups are represented by an array of objects with an items property, which itself is an array of individual items for each group. An extra property, such as value, can be provided for the heading text when rendering the group label.
interface ProduceGroupItem {
value: string;
items: string[];
}
const groups: ProduceGroupItem[] = [
{
value: 'Fruits',
items: ['Apple', 'Banana', 'Orange'],
},
{
value: 'Vegetables',
items: ['Carrot', 'Lettuce', 'Spinach'],
},
];API reference
Root
| Name | Type | Default | Description |
|---|---|---|---|
childrenOptional | React.ReactNode | — | — |
inputRefOptional | React.Ref<HTMLInputElement> | undefined | — | A ref to access the hidden input element. |
nameOptional | string | undefined | — | Identifies the field when a form is submitted. |
autoCompleteOptional | string | undefined | — | Provides a hint to the browser for autofill. @see https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/autocomplete |
idOptional | string | undefined | — | The id of the Select. |
requiredOptional | boolean | undefined | false | Whether the user must choose a value before submitting a form. |
readOnlyOptional | boolean | undefined | false | Whether the user should be unable to choose a different option from the select popup. |
disabledOptional | boolean | undefined | false | Whether the component should ignore user interaction. |
multipleOptional | Multiple | undefined | false | Whether multiple items can be selected. |
highlightItemOnHoverOptional | boolean | undefined | true | Whether moving the pointer over items should highlight them. Disabling this prop allows CSS `:hover` to be differentiated from the `:focus` (`data-highlighted`) state. |
defaultOpenOptional | boolean | undefined | false | Whether the select popup is initially open. To render a controlled select popup, use the `open` prop instead. |
onOpenChangeOptional | ((open: boolean, eventDetails: SelectRootChangeEventDetails) => void) | undefined | — | Event handler called when the select popup is opened or closed. |
onOpenChangeCompleteOptional | ((open: boolean) => void) | undefined | — | Event handler called after any animations complete when the select popup is opened or closed. |
openOptional | boolean | undefined | — | Whether the select popup is currently open. |
modalOptional | boolean | undefined | true | Determines if the select enters a modal state when open. - `true`: user interaction is limited to the select: document page scroll is locked and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. |
actionsRefOptional | React.RefObject<SelectRootActions | null> | undefined | — | A ref to imperative actions. - `unmount`: When specified, the select will not be unmounted when closed. Instead, the `unmount` function must be called to unmount the select manually. Useful when the select's animation is controlled by an external library. |
Trigger
| Name | Type | Default | Description |
|---|---|---|---|
childrenOptional | React.ReactNode | — | — |
disabledOptional | boolean | undefined | — | Whether the component should ignore user interaction. |
Positioner
| Name | Type | Default | Description |
|---|---|---|---|
alignItemWithTriggerOptional | boolean | undefined | true | Whether the positioner overlaps the trigger so the selected item's text is aligned with the trigger's value text. This only applies to mouse input and is automatically disabled if there is not enough space. |
Popup
| Name | Type | Default | Description |
|---|---|---|---|
childrenOptional | React.ReactNode | — | — |
Item
| Name | Type | Default | Description |
|---|---|---|---|
childrenOptional | React.ReactNode | — | — |
valueOptional | any | null | A unique value that identifies this select item. |
disabledOptional | boolean | undefined | false | Whether the component should ignore user interaction. |
labelOptional | string | undefined | — | Specifies the text label to use when the item is matched during keyboard text navigation. Defaults to the item text content if not provided. |
ItemIndicator
| Name | Type | Default | Description |
|---|---|---|---|
childrenOptional | React.ReactNode | — | — |
keepMountedOptional | boolean | undefined | — | Whether to keep the HTML element in the DOM when the item is not selected. |
ScrollUpArrow
| Name | Type | Default | Description |
|---|---|---|---|
keepMountedOptional | boolean | undefined | false | Whether to keep the HTML element in the DOM while the select popup is not scrollable. |
ScrollDownArrow
| Name | Type | Default | Description |
|---|---|---|---|
keepMountedOptional | boolean | undefined | false | Whether to keep the HTML element in the DOM while the select popup is not scrollable. |