Select
A button that opens a listbox to choose one option, or several.
Select lets the user pick a value from a set that is too long or too dynamic
for a Segment, and only takes effect once they choose. With
selectionMode="multiple" they can pick several. It is built from parts: a
trigger that shows the current value through MotionSelectValue, and a
SelectPopover of SelectItems. The trigger is either a SelectTrigger,
styled by the Select's variant, or any Button.
Examples
Basic
<Select aria-label="Fruit" placeholder="Pick a fruit">
<Button variant="outline" className="min-w-40 justify-between">
<MotionSelectValue />
</Button>
<SelectPopover>
<SelectItem id="apple">Apple</SelectItem>
<SelectItem id="banana">Banana</SelectItem>
<SelectItem id="cherry">Cherry</SelectItem>
</SelectPopover>
</Select>Item icons, trailing content, and destructive items
<Select aria-label="Access" defaultValue="member">
<Button variant="outline" className="min-w-48 justify-between">
<MotionSelectValue />
</Button>
<SelectPopover>
<SelectItem id="admin" startIcon={<UserAddIcon />} endContent={<span className="text-label-regular text-bds-fg-muted">Full access</span>}>
Admin
</SelectItem>
<SelectItem id="member" startIcon={<UserGroupIcon />}>
Member
</SelectItem>
<SelectItem id="none" variant="destructive" startIcon={<UserDownIcon />}>
No Access
</SelectItem>
</SelectPopover>
</Select>startIcon is sized to the item's line height and sits before the label.
endContent trails the label and the selected check — use it for a short hint,
not a second control. variant="destructive" colours an item red so a
consequential choice reads as one before it is picked.
MotionSelectValue animates between the placeholder and the selected label, so
the trigger updates in place instead of jumping. The Button is an ordinary
trigger: style it with the variants you already use, give it a width so it does
not resize as the value changes, and add justify-between to start-align the
value and push an end icon to the far edge.
Variants
variant on the Select styles its SelectTrigger, SelectPopover, and a
FieldLabel inside: filled (the default) and outline for forms, pill for
filters. The trigger shows the value or placeholder and a down arrow that turns
while the list is open. In a form, the field variants make the Select look like
the Text Field beside it. Stack the label above the
trigger with a column and Text Field's gap-1.5, or the fieldStyles() helper
from Text Field in a client component.
Filled
The default: the gray, 40px field of dense product forms.
<Select placeholder="Select" className="flex flex-col gap-1.5">
<FieldLabel>Region</FieldLabel>
<SelectTrigger />
<SelectPopover>
<SelectItem id="us">United States</SelectItem>
<SelectItem id="ie">Ireland</SelectItem>
<SelectItem id="sg">Singapore</SelectItem>
</SelectPopover>
</Select>Outline
The transparent, bordered 36px field of marketing forms, matching an Outline Text Field.
<div className="flex flex-col gap-5">
<Select variant="outline" placeholder="Select" className="flex flex-col gap-1.5">
<FieldLabel>Company Size</FieldLabel>
<SelectTrigger />
<SelectPopover>
<SelectItem id="small">1–50</SelectItem>
<SelectItem id="medium">51–500</SelectItem>
<SelectItem id="large">500+</SelectItem>
</SelectPopover>
</Select>
<TextField variant="outline" label="Company Name" placeholder="Acme" />
</div>Pill
A 36px rounded trigger for filter bars, usually with a fixed label, the name of the filter, as its children instead of the value. It fills gray once any option is chosen, so a row of pills shows which filters are on. Its popover is 208px wide, with tighter corners, and drops in slightly from the trigger. The pill's shape, border and fills come from one shared style, so it matches the other pill-shaped filter controls in BDS.
<Select aria-label="Infra" variant="pill" selectionMode="multiple">
<SelectTrigger>Infra</SelectTrigger>
<SelectPopover>
<SelectItem id="bridges">Bridges</SelectItem>
<SelectItem id="oracles">Oracles</SelectItem>
<SelectItem id="wallets">Wallets</SelectItem>
</SelectPopover>
</Select>variant only styles a SelectTrigger, SelectPopover, and FieldLabel. A
Button trigger keeps its own variant. Outline also makes the popover as wide
as the trigger.
Multiple selection
<Select
aria-label="Fruit"
selectionMode="multiple"
placeholder="Pick fruit"
defaultValue={['apple', 'cherry']}
>
<SelectTrigger />
<SelectPopover>
<SelectItem id="apple">Apple</SelectItem>
<SelectItem id="banana">Banana</SelectItem>
<SelectItem id="cherry">Cherry</SelectItem>
</SelectPopover>
</Select>With selectionMode="multiple", value and onChange use an array of item ids,
and the list stays open while the user picks. Each item leads with a small
Checkbox box instead of the trailing check, and
unselected labels are muted so the chosen ones stand out. MotionSelectValue
lists the chosen labels.
The option boxes follow the Checkbox tone set through CheckboxPropsContext,
so a product that sets tone="neutral" once gets matching boxes in Select.
<CheckboxPropsContext value={{ tone: 'neutral' }}>
<Select
aria-label="Fruit"
selectionMode="multiple"
placeholder="Pick fruit"
defaultValue={['apple', 'cherry']}
>
<SelectTrigger />
<SelectPopover>
<SelectItem id="apple">Apple</SelectItem>
<SelectItem id="banana">Banana</SelectItem>
<SelectItem id="cherry">Cherry</SelectItem>
</SelectPopover>
</Select>
</CheckboxPropsContext>Select all
Select has no built-in select-all row. Compose one: control the value, and add a first item whose press selects every option, or clears them when all are already chosen. Keep its id out of the value you store.
const options = ['bridges', 'oracles', 'wallets'];
const [value, setValue] = useState<Key[]>([]);
const allSelected = options.every((id) => value.includes(id));
<Select
aria-label="Infra"
variant="pill"
selectionMode="multiple"
value={allSelected ? ['all', ...options] : value}
onChange={(keys) => {
const toggledAll = keys.includes('all') !== allSelected;
if (toggledAll) setValue(allSelected ? [] : options);
else setValue(keys.filter((key) => key !== 'all'));
}}
>
<SelectTrigger>Infra</SelectTrigger>
<SelectPopover>
<SelectItem id="all">All Infra</SelectItem>
<Separator className="mx-2 my-1 border-bds-border-default" />
{options.map((id) => (
<SelectItem key={id} id={id}>
{id}
</SelectItem>
))}
</SelectPopover>
</Select>;Separator comes from react-aria-components.
Select items use the medium row of listItemStyles, the same row as a
medium Menu. The large row is for
navigation menus only.
When to use
Use Select to pick one value, or several with selectionMode="multiple", from
a set that is too long or too dynamic to show all at once. In a form, use a
SelectTrigger with the same variant as the form's text fields: Filled for
dense product forms, Outline for marketing forms such as the base.org contact
form. Use the pill variant for a filter in a row of filters.
Use something else when:
- There are two to five fixed options. Use Segment.
- The items fire actions instead of setting a value. Use Menu.
- The value is free text. Use Text Field.
- A short set of filters should all stay visible and toggle in place. Use Tag Group.
- A filter has no sub-options to choose from. Use a Segment or a toggle Button instead of a pill that opens a one-item list.
Usage
Do
Give the Select an accessible name — visible label or aria-label — and a
placeholder that names the choice, not a value.
Don't
Reach for Select when there are two or three fixed options. A Segment shows them all at once and saves a click.
Do
Label a pill with the name of the filter and let its fill show that options are chosen.
Don't
Use the pill variant inside a form. Use a field variant so it lines up with the other fields.
Props
Select
| Prop | Type | Default |
|---|---|---|
allowsEmptyCollectionWhether the select should be allowed to be open when the collection is empty. | Typeboolean | Default— |
autoCompleteDescribes the type of autocomplete functionality the input should provide if any. See | Typestring | Default— |
childrenThe children of the component. A function may be provided to alter the children based on | TypeChildrenOrFunction<SelectValueRenderProps<T>> | Default— |
classNameThe CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the | TypeClassNameOrFunction<SelectValueRenderProps<T>> | Default— |
defaultOpenSets the default open state of the menu. | Typeboolean | Default— |
defaultSelectedKeyThe initial selected key in the collection (uncontrolled). | TypeKey | null | Default— |
defaultValueThe default value (uncontrolled). | TypeKey | readonly Key[] | null | Default— |
disabledKeysThe item keys that are disabled. These items cannot be selected, focused, or otherwise | TypeIterable<Key> | Default— |
isDisabledWhether the input is disabled. | Typeboolean | Default— |
isInvalidWhether the input value is invalid. | Typeboolean | Default— |
isOpenSets the open state of the menu. | Typeboolean | Default— |
isRequiredWhether user input is required on the input before form submission. | Typeboolean | Default— |
placeholderTemporary text that occupies the select when it is empty. | Typestring | Default'Select an item' (localized) |
selectedKeyThe currently selected key in the collection (controlled). | TypeKey | null | Default— |
selectionModeWhether single or multiple selection is enabled. | Type"single" | "multiple" | Default'single' |
shouldCloseOnSelectWhether the Select should close when an item is selected. Defaults to true if selectionMode is | Typeboolean | Default— |
validateA function that returns an error message if a given value is invalid. | Type((value: ValidationType<M>) => true | ValidationError | null) | Default— |
validationBehaviorWhether to use native HTML form validation to prevent form submission | Type"native" | "aria" | Default'native' |
variantStyles the `SelectTrigger`, `SelectPopover`, and `FieldLabel` inside. | Type"filled" | "outline" | "pill" | Defaultfilled |
SelectItem
In multiple selection the item leads with a checkbox and mutes its label while unselected.
| Prop | Type | Default |
|---|---|---|
childrenThe children of the component. A function may be provided to alter the children based on | TypeChildrenOrFunction<SelectValueRenderProps<T>> | Default— |
classNameThe CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the | TypeClassNameOrFunction<ListBoxItemRenderProps> | Default— |
endContent | TypeReactNode | Default— |
hrefA URL to link to. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href). | Typestring | Default— |
idThe unique id of the item. | TypeKey | Default— |
isDisabledWhether the item is disabled. | Typeboolean | Default— |
relThe relationship between the linked resource and the current page. See | Typestring | Default— |
startIcon | TypeReactNode | Default— |
targetThe target window for the link. See | TypeHTMLAttributeAnchorTarget | Default— |
textValueA string representation of the item's contents, used for features like typeahead. | Typestring | Default— |
variant | Type"default" | "destructive" | null | Defaultdefault |
SelectTrigger
| Prop | Type | Default |
|---|---|---|
childrenThe children of the component. A function may be provided to alter the children based on | TypeChildrenOrFunction<SelectValueRenderProps<T>> | Default— |
classNameThe CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the | TypeClassNameOrFunction<ButtonRenderProps> | Default— |
isDisabledWhether the button is disabled. | Typeboolean | Default— |
isPendingWhether the button is in a pending state. This disables press and hover events | Typeboolean | Default— |
typeThe behavior of the button when used in an HTML form. | Type"button" | "submit" | "reset" | Default'button' |
variantDefaults to the enclosing Select's variant. | Type"filled" | "outline" | "pill" | Default— |
SelectPopover
The popover sizes to its options and is never narrower than 200px. In an
Outline Select it also matches the trigger's width, so a field-width trigger
opens a field-width list. Pass a width class to override either: for example
w-(--trigger-width) on a Filled Select, or w-auto on an Outline one. The
list scrolls within the popover's available height without a scrollbar,
fading the edge where more options are hidden. The list, not the popover,
carries the inner padding. In the pill variant it is 208px wide and uses
Popover's drop entrance.
| Prop | Type | Default |
|---|---|---|
arrowBoundaryOffsetThe minimum distance the arrow's edge should be from the edge of the overlay element. | Typenumber | Default0 |
arrowRefA ref for the popover arrow element. | TypeRefObject<Element | null> | Default— |
boundaryElementElement that that serves as the positioning boundary. | TypeElement | Defaultdocument.body |
childrenThe children of the component. A function may be provided to alter the children based on | TypeChildrenOrFunction<SelectValueRenderProps<T>> | Default— |
classNameThe CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the | TypeClassNameOrFunction<PopoverRenderProps> | Default— |
containerPaddingThe placement padding that should be applied between the element and its | Typenumber | Default12 |
crossOffsetThe additional offset applied along the cross axis between the element and its | Typenumber | Default0 |
defaultOpenWhether the overlay is open by default (uncontrolled). | Typeboolean | Default— |
entranceHow the surface enters. `scale` grows it from the trigger; `drop` also | Type"scale" | "drop" | Default'scale' |
getTargetRectOverrides the target element's bounding rectangle. Useful for positioning relative to | Type((target: Element) => DOMRect | null) | Defaulttarget.getBoundingClientRect() |
isEnteringWhether the popover is currently performing an entry animation. | Typeboolean | Default— |
isExitingWhether the popover is currently performing an exit animation. | Typeboolean | Default— |
isKeyboardDismissDisabledWhether pressing the escape key to close the popover should be disabled. | Typeboolean | Defaultfalse |
isNonModalWhether the popover is non-modal, i.e. elements outside the popover may be | Typeboolean | Default— |
isOpenWhether the overlay is open by default (controlled). | Typeboolean | Default— |
maxHeightThe maxHeight specified for the overlay element. | Typenumber | Default— |
offsetThe additional offset applied along the main axis between the element and its | Typenumber | Default8 |
placementThe placement of the element with respect to its anchor element. | Typeenum | Default'bottom' |
scrollRefA ref for the scrollable region within the overlay. | TypeRefObject<Element | null> | DefaultoverlayRef |
shouldCloseOnInteractOutsideWhen user interacts with the argument element outside of the popover ref, | Type((element: Element) => boolean) | Default— |
shouldFlipWhether the element should flip its orientation (e.g. top to bottom or left to right) when | Typeboolean | Defaulttrue |
shouldSkipAnimationWhether the popover should appear and disappear without an entry or exit animation. This is | Typeboolean | Default— |
shouldUpdatePositionWhether the overlay should update its position automatically. | Typeboolean | Defaulttrue |
triggerThe name of the component that triggered the popover. This is reflected on the element | Typestring | Default— |
triggerRefThe ref for the element which the popover positions itself with respect to. | TypeRefObject<Element | null> | Default— |
UNSTABLE_portalContainerThe container element in which the overlay portal will be placed. This may have unknown | TypeElement | Defaultdocument.body |
MotionSelectValue
| Prop | Type | Default |
|---|---|---|
childrenThe children of the component. A function may be provided to alter the children based on | TypeChildrenOrFunction<SelectValueRenderProps<T>> | Default— |
classNameThe CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the | TypeClassNameOrFunction<SelectValueRenderProps<T>> | Default— |
Accessibility
Select wraps React Aria's Select, which manages the listbox, roving focus,
Escape-to-close, and type-ahead, and keeps the trigger and options wired
together. Each SelectItem needs an id, and a Select without a visible label
needs aria-label.
SelectTrigger is a button named by the FieldLabel and the current value.
Its down arrow is decorative and hidden from assistive tech, and keyboard focus
shows a 2px ring. An invalid Select turns the trigger's border red; pair it with
an error message so the state is not shown by colour alone.
With selectionMode="multiple" the listbox is marked multi-selectable and each
option reports whether it is selected. The checkbox in each option is drawn
with CheckboxIndicator, not a nested Checkbox: it is hidden from assistive
technology and never takes focus, so each row stays one option rather than an
option wrapping a second control. A pill whose label is the filter name still
needs an aria-label on the Select that names it.
When the reader prefers reduced motion, MotionSelectValue swaps the label
instantly instead of sliding it in, the arrow flips without turning, and the
pill popover appears without moving.