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.

Region
<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.

Company Size
<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

PropTypeDefault
allowsEmptyCollection

Whether the select should be allowed to be open when the collection is empty.

TypebooleanDefault—
autoComplete

Describes the type of autocomplete functionality the input should provide if any. See

TypestringDefault—
children

The children of the component. A function may be provided to alter the children based on

TypeChildrenOrFunction<SelectValueRenderProps<T>>Default—
className

The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the

TypeClassNameOrFunction<SelectValueRenderProps<T>>Default—
defaultOpen

Sets the default open state of the menu.

TypebooleanDefault—
defaultSelectedKey

The initial selected key in the collection (uncontrolled).

TypeKey | nullDefault—
defaultValue

The default value (uncontrolled).

TypeKey | readonly Key[] | nullDefault—
disabledKeys

The item keys that are disabled. These items cannot be selected, focused, or otherwise

TypeIterable<Key>Default—
isDisabled

Whether the input is disabled.

TypebooleanDefault—
isInvalid

Whether the input value is invalid.

TypebooleanDefault—
isOpen

Sets the open state of the menu.

TypebooleanDefault—
isRequired

Whether user input is required on the input before form submission.

TypebooleanDefault—
placeholder

Temporary text that occupies the select when it is empty.

TypestringDefault'Select an item' (localized)
selectedKey

The currently selected key in the collection (controlled).

TypeKey | nullDefault—
selectionMode

Whether single or multiple selection is enabled.

Type"single" | "multiple"Default'single'
shouldCloseOnSelect

Whether the Select should close when an item is selected. Defaults to true if selectionMode is

TypebooleanDefault—
validate

A function that returns an error message if a given value is invalid.

Type((value: ValidationType<M>) => true | ValidationError | null)Default—
validationBehavior

Whether to use native HTML form validation to prevent form submission

Type"native" | "aria"Default'native'
variant

Styles 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.

PropTypeDefault
children

The children of the component. A function may be provided to alter the children based on

TypeChildrenOrFunction<SelectValueRenderProps<T>>Default—
className

The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the

TypeClassNameOrFunction<ListBoxItemRenderProps>Default—
endContentTypeReactNodeDefault—
href

A URL to link to. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#href).

TypestringDefault—
id

The unique id of the item.

TypeKeyDefault—
isDisabled

Whether the item is disabled.

TypebooleanDefault—
rel

The relationship between the linked resource and the current page. See

TypestringDefault—
startIconTypeReactNodeDefault—
target

The target window for the link. See

TypeHTMLAttributeAnchorTargetDefault—
textValue

A string representation of the item's contents, used for features like typeahead.

TypestringDefault—
variantType"default" | "destructive" | nullDefaultdefault

SelectTrigger

PropTypeDefault
children

The children of the component. A function may be provided to alter the children based on

TypeChildrenOrFunction<SelectValueRenderProps<T>>Default—
className

The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the

TypeClassNameOrFunction<ButtonRenderProps>Default—
isDisabled

Whether the button is disabled.

TypebooleanDefault—
isPending

Whether the button is in a pending state. This disables press and hover events

TypebooleanDefault—
type

The behavior of the button when used in an HTML form.

Type"button" | "submit" | "reset"Default'button'
variant

Defaults 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.

PropTypeDefault
arrowBoundaryOffset

The minimum distance the arrow's edge should be from the edge of the overlay element.

TypenumberDefault0
arrowRef

A ref for the popover arrow element.

TypeRefObject<Element | null>Default—
boundaryElement

Element that that serves as the positioning boundary.

TypeElementDefaultdocument.body
children

The children of the component. A function may be provided to alter the children based on

TypeChildrenOrFunction<SelectValueRenderProps<T>>Default—
className

The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the

TypeClassNameOrFunction<PopoverRenderProps>Default—
containerPadding

The placement padding that should be applied between the element and its

TypenumberDefault12
crossOffset

The additional offset applied along the cross axis between the element and its

TypenumberDefault0
defaultOpen

Whether the overlay is open by default (uncontrolled).

TypebooleanDefault—
entrance

How the surface enters. `scale` grows it from the trigger; `drop` also

Type"scale" | "drop"Default'scale'
getTargetRect

Overrides the target element's bounding rectangle. Useful for positioning relative to

Type((target: Element) => DOMRect | null)Defaulttarget.getBoundingClientRect()
isEntering

Whether the popover is currently performing an entry animation.

TypebooleanDefault—
isExiting

Whether the popover is currently performing an exit animation.

TypebooleanDefault—
isKeyboardDismissDisabled

Whether pressing the escape key to close the popover should be disabled.

TypebooleanDefaultfalse
isNonModal

Whether the popover is non-modal, i.e. elements outside the popover may be

TypebooleanDefault—
isOpen

Whether the overlay is open by default (controlled).

TypebooleanDefault—
maxHeight

The maxHeight specified for the overlay element.

TypenumberDefault—
offset

The additional offset applied along the main axis between the element and its

TypenumberDefault8
placement

The placement of the element with respect to its anchor element.

TypeenumDefault'bottom'
scrollRef

A ref for the scrollable region within the overlay.

TypeRefObject<Element | null>DefaultoverlayRef
shouldCloseOnInteractOutside

When user interacts with the argument element outside of the popover ref,

Type((element: Element) => boolean)Default—
shouldFlip

Whether the element should flip its orientation (e.g. top to bottom or left to right) when

TypebooleanDefaulttrue
shouldSkipAnimation

Whether the popover should appear and disappear without an entry or exit animation. This is

TypebooleanDefault—
shouldUpdatePosition

Whether the overlay should update its position automatically.

TypebooleanDefaulttrue
trigger

The name of the component that triggered the popover. This is reflected on the element

TypestringDefault—
triggerRef

The ref for the element which the popover positions itself with respect to.

TypeRefObject<Element | null>Default—
UNSTABLE_portalContainer

The container element in which the overlay portal will be placed. This may have unknown

TypeElementDefaultdocument.body

MotionSelectValue

PropTypeDefault
children

The children of the component. A function may be provided to alter the children based on

TypeChildrenOrFunction<SelectValueRenderProps<T>>Default—
className

The 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.