---
name: Select
package: '@wordpress/ui'
category: '@wordpress-ui'
status: stable
canonical: 'https://system.automattic.design/components/select/'
storybook: 'https://wordpress.github.io/gutenberg/?path=/docs/design-system-components-form-primitives-select--docs'
github: 'https://github.com/WordPress/gutenberg/tree/trunk/packages/ui/src/form'
---

# Select

A component that lets users choose one option from a list.

Prefer `SelectControl` when using with a standard label and description.

When using object values, pass an `items` array so `Select.Trigger` can
auto-resolve the selected item's label. By default, items should use a
`{ value, label }` shape, or provide `itemToStringLabel` for a custom shape.

Object values are compared with `Object.is` by default, so use the same
object references for `value` / `defaultValue` and `Select.Item` values, or
provide `isItemEqualToValue`.

```tsx
import { Select } from '@wordpress/ui';
```

## Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` | `string` | — | Identifies the field when a form is submitted. |
| `form` | `string` | — | Identifies the form that owns the hidden input. Useful when the select is rendered outside the form. |
| `children` | `ReactNode` | — |  |
| `id` | `string` | — | The id of the Select. |
| `readOnly` | `boolean` | `false` | Whether the user should be unable to choose a different option from the select popup. |
| `defaultValue` | `Value \| null \| undefined` | — | The uncontrolled value of the select when it's initially rendered.<br>To render a controlled select, use the `value` prop instead. |
| `required` | `boolean` | `false` | Whether the user must choose a value before submitting a form. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `value` | `Value \| null \| undefined` | — | The value of the select. Use when controlled. |
| `onValueChange` | `(value: Value \| null, eventDetails: SelectRootChangeEventDetails) => void` | — | Event handler called when the value of the select changes. |
| `open` | `boolean` | — | Whether the select popup is currently open. |
| `defaultOpen` | `boolean` | `false` | Whether the select popup is initially open.<br>To render a controlled select popup, use the `open` prop instead. |
| `onOpenChange` | `(open: boolean, eventDetails: SelectRootChangeEventDetails) => void` | — | Event handler called when the select popup is opened or closed. |
| `autoComplete` | `string` | — | Provides a hint to the browser for autofill. |
| `itemToStringValue` | `(itemValue: Value) => string` | — | When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for form submission. If the shape of the object is `{ value, label }`, the value will be used automatically without needing to specify this prop. |
| `isItemEqualToValue` | `(itemValue: Value, value: Value) => boolean` | — | Custom comparison logic used to determine if a select item value matches the current selected value. Useful when item values are objects without matching referentially. Defaults to `Object.is` comparison. |
| `itemToStringLabel` | `(itemValue: Value) => string` | — | When the item values are objects (`<Select.Item value={object}>`), this function converts the object value to a string representation for display in the trigger. If the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop. |
| `highlightItemOnHover` | `boolean` | `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. |
| `actionsRef` | `RefObject<SelectRootActions \| null>` | — | A ref to imperative actions. - `unmount`: Manually unmounts the select. Call this after any externally controlled closing animation finishes. |
| `items` | `Record<string, ReactNode> \| readonly { label: ReactNode; value: any; }[] \| readonly Group<any>[] \| undefined` | — | Data structure of the items rendered in the select popup. When specified, `<Select.Value>` renders the label of the selected item instead of the raw value. |
| `onOpenChangeComplete` | `(open: boolean) => void` | — | Event handler called after any animations complete when the select popup is opened or closed. |
| `inputRef` | `Ref<HTMLInputElement> \| undefined` | — | A ref to access the hidden input element. |
| `modal` | `boolean` | `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.<br>On touch devices, a `true` modal blocks outside taps but leaves the page scrollable unless the popup spans nearly the full viewport width, matching native iOS behavior. |


## Examples

### Default

```tsx
const Default = () => <Select.Root items={defaultItems}>{[
        <Select.Trigger aria-label="Item" key="trigger" />,
        <Select.Popup key="popup">
            { defaultItems.map( ( item ) => (
                <Select.Item key={ item.value } value={ item }>
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### Compact

```tsx
const Compact = () => <Select.Root>{[
        <Select.Trigger size="compact" aria-label="Item" key="trigger" />,
        <Select.Popup key="popup">
            { defaultItems.map( ( item ) => (
                <Select.Item key={ item.value } value={ item }>
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### Minimal

The `minimal` variant must be used judiciously, because in many
contexts it can be unclear to users that it is a select trigger.

Combined with the `small` size, `minimal` can be used to create a
very low-profile `Select`, intended for rare use cases like
a pagination control.

```tsx
const Minimal = () => <Select.Root defaultValue="1">{[
        <Select.Trigger
            size="small"
            variant="minimal"
            aria-label="Item"
            key="trigger"
        />,
        <Select.Popup width="content" key="popup">
            { Array.from( { length: 6 }, ( _, index ) => (
                <Select.Item
                    key={ index }
                    value={ `${ index + 1 }` }
                    size="small"
                >
                    <Select.ItemLabel>{ `${
                        index + 1
                    }` }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### Grouped

Options can be organized into labeled groups with `Select.Group`
and `Select.GroupLabel`.

```tsx
const Grouped = () => <Select.Root items={groupedItems.flatMap( ( group ) => group.items )}>{[
        <Select.Trigger aria-label="Item" key="trigger" />,
        <Select.Popup key="popup">
            { groupedItems.map( ( group ) => (
                <Select.Group key={ group.label }>
                    <Select.GroupLabel>{ group.label }</Select.GroupLabel>
                    { group.items.map( ( item ) => (
                        <Select.Item key={ item.value } value={ item }>
                            <Select.ItemLabel>
                                { item.label }
                            </Select.ItemLabel>
                        </Select.Item>
                    ) ) }
                </Select.Group>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### With Custom Placeholder

Use the `placeholder` prop on `Select.Trigger` to show text when no
value is selected. The default placeholder is `"Select"`.

```tsx
const WithCustomPlaceholder = () => <Select.Root items={defaultItems}>{[
        <Select.Trigger
            placeholder="Choose an item"
            aria-label="Item"
            key="trigger"
        />,
        <Select.Popup key="popup">
            { defaultItems.map( ( item ) => (
                <Select.Item key={ item.value } value={ item }>
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### With Null Value Option

Use a `null` item when users should be able to clear the selected value from
the popup. When `items` includes a `null` item, its label is used as the
placeholder text.

```tsx
const WithNullValueOption = () => <Select.Root items={nullValueOptionItems}>{[
        <Select.Trigger aria-label="Item" key="trigger" />,
        <Select.Popup key="popup">
            { nullValueOptionItems.map( ( item ) => (
                <Select.Item
                    key={ item.value ?? 'null' }
                    value={ item.value }
                >
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### Labeling

When accessibly labeling a `Select`, note that the label must be associated with the `Select.Trigger`,
not the `Select.Root`.

Whether labeling with `aria-label`, `htmlFor`, or `aria-labelledby`, the association must be made to the `Select.Trigger`.

```tsx
const Labeling = () => <Select.Root>{[
        <Select.Trigger aria-label="User role" key="trigger" />,
        <Select.Popup key="popup">
            { defaultItems.map( ( item ) => (
                <Select.Item key={ item.value } value={ item }>
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### With Overflow

```tsx
const WithOverflow = () => <Select.Root items={overflowItems} defaultValue={overflowItems[ 0 ]}>{[
        <Select.Trigger aria-label="Item" key="trigger" />,
        <Select.Popup key="popup">
            { overflowItems.map( ( item ) => (
                <Select.Item key={ item.value } value={ item }>
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### With Long List

```tsx
const WithLongList = () => <Select.Root items={longListItems} defaultValue={longListItems[ 17 ]}>{[
        <Select.Trigger aria-label="Item" key="trigger" />,
        <Select.Popup key="popup">
            { longListItems.map( ( item ) => (
                <Select.Item key={ item.value } value={ item }>
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### Disabled

```tsx
const Disabled = () => <Select.Root defaultValue={defaultItems[ 0 ]} disabled>{[
        <Select.Trigger aria-label="Item" key="trigger" />,
        <Select.Popup key="popup">
            { defaultItems.map( ( item ) => (
                <Select.Item key={ item.value } value={ item }>
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### With Disabled Item

```tsx
const WithDisabledItem = () => <Select.Root items={disabledItemItems} defaultValue={disabledItemItems[ 0 ]}>{[
        <Select.Trigger aria-label="Item" key="trigger" />,
        <Select.Popup key="popup">
            { disabledItemItems.map( ( item ) => (
                <Select.Item
                    key={ item.value }
                    value={ item }
                    disabled={ item.disabled }
                >
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### With Custom Trigger And Item

For custom needs, a `Select.Trigger` can take a custom render function as its
children. Put custom item content inside `Select.ItemLabel`.

```tsx
const WithCustomTriggerAndItem = () => <Select.Root items={customOptions} defaultValue={customOptions[ 0 ]}>{[
        <Select.Trigger aria-label="Item" key="trigger">
            { ( item ) => (
                <span
                    style={ {
                        display: 'flex',
                        alignItems: 'center',
                        gap: 8,
                    } }
                >
                    <img
                        src={ `https://gravatar.com/avatar/?d=initials&name=${ item.value }` }
                        alt=""
                        width="20"
                        style={ {
                            borderRadius: '50%',
                        } }
                    />

                    { item.label }
                </span>
            ) }
        </Select.Trigger>,
        <Select.Popup key="popup">
            { customOptions.map( ( item ) => (
                <Select.Item key={ item.value } value={ item }>
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### With Item Description

Use `Select.ItemDescription` for supplementary text that should be
announced as a description rather than part of the item name.

```tsx
const WithItemDescription = () => <Select.Root items={stockItems}>{[
        <Select.Trigger aria-label="Item" key="trigger" />,
        <Select.Popup key="popup">
            { stockItems.map( ( item ) => (
                <Select.Item key={ item.value } value={ item }>
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                    <Select.ItemDescription>
                        { item.description }
                    </Select.ItemDescription>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```

### With Custom z-index

Popovers in Gutenberg are managed with explicit z-index values, which can
create situations where a select popup renders below another popover when
you want it above.

The `--wp-ui-select-z-index` CSS variable controls the z-index of the
`Select` positioner. Override it either:

- **Globally**, by setting the variable on `:root` or `body` (raises every
  `Select` popover in the page), or
- **Per instance**, by passing a `Select.Portal` with a `style` (or
  `className`) to `Select.Popup`'s `portal` prop. The variable cascades
  from the portal wrapper to everything rendered inside it.

This story demonstrates the per-instance approach.

```tsx
const WithCustomZIndex = () => <Select.Root>{[
        <Select.Trigger aria-label="Item" key="trigger" />,
        <Select.Popup
            portal={
                <Select.Portal
                    style={ { '--wp-ui-select-z-index': '9999' } }
                />
            }
            key="popup"
        >
            { defaultItems.map( ( item ) => (
                <Select.Item key={ item.value } value={ item }>
                    <Select.ItemLabel>{ item.label }</Select.ItemLabel>
                </Select.Item>
            ) ) }
        </Select.Popup>,
    ]}</Select.Root>;
```
