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.

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

View on Storybook

View source on GitHub

PropsPermalink to this section

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

readOnlyfalse

boolean

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.

To render a controlled select, use the value prop instead.

requiredfalse

boolean

Whether the user must choose a value before submitting a form.

disabledfalse

boolean

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.

defaultOpenfalse

boolean

Whether the select popup is initially open.

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.

highlightItemOnHovertrue

boolean

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.

modaltrue

boolean

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.

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.

ExamplesPermalink to this section

DefaultPermalink to this section

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>;

CompactPermalink to this section

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>;

MinimalPermalink to this section

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.

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>;

GroupedPermalink to this section

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

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 PlaceholderPermalink to this section

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

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 OptionPermalink to this section

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.

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>;

LabelingPermalink to this section

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.

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 OverflowPermalink to this section

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 ListPermalink to this section

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>;

DisabledPermalink to this section

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 ItemPermalink to this section

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 ItemPermalink to this section

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

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 DescriptionPermalink to this section

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

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-indexPermalink to this section

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.

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>;