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

# SearchableChipSelectControl

A complete searchable multi-select field with chips, integrated label,
and description.

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

## Props

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `form` | `string` | — | Identifies the form that owns the internal input. Useful when the combobox is rendered outside the form. |
| `filter` | `((item: Item, query: string, itemToString?: ((item: Item) => string) \| undefined) => boolean) \| null \| undefined` | — | Filter function used to match items vs input query. Receives the source item, which is the derived value's item when `items` is a `createItems()` collection, and the item itself otherwise. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `name` | `string` | — | Identifies the field when a form is submitted. |
| `value` | `Item[] \| null \| undefined` | — | The selected value of the combobox. Use when controlled. |
| `defaultValue` | `Item[] \| null \| undefined` | — | The uncontrolled selected value of the combobox when it's initially rendered.<br>To render a controlled combobox, use the `value` prop instead. |
| `id` | `string` | — | The id of the component. |
| `grid` | `boolean` | `false` | Whether list items are presented in a grid layout. When enabled, arrow keys navigate across rows and columns inferred from DOM rows. |
| `inline` | `boolean` | `false` | Whether the list is rendered inline without using the component's own popup.<br>Specify `open` unconditionally in conjunction with this prop so the list is considered visible: `<Combobox.Root inline open>`<br>In a `Combobox.Root` > `Dialog.Root` composition, bind the Combobox's `open` and `onOpenChange` props to the `Dialog`'s `open` and `onOpenChange` state instead so the component resets its transient state (filter query, highlighted item, and input value) when the dialog closes. |
| `open` | `boolean` | — | Whether the popup is currently open. Use when controlled. |
| `autoComplete` | `string` | — | Provides a hint to the browser for autofill. |
| `readOnly` | `boolean` | `false` | Whether the user should be unable to choose a different option from the popup. |
| `required` | `boolean` | `false` | Whether the user must choose a value before submitting a form. |
| `defaultOpen` | `boolean` | `false` | Whether the popup is initially open.<br>To render a controlled popup, use the `open` prop instead. |
| `limit` | `number` | `-1` | The maximum number of items to display in the list. |
| `locale` | `LocalesArgument` | — | The locale to use for string comparison. Defaults to the user's runtime locale. |
| `onValueChange` | `(value: Item[], eventDetails: ChangeEventDetails) => void` | — | Event handler called when the selected value of the combobox changes. |
| `onOpenChange` | `(open: boolean, eventDetails: ChangeEventDetails) => void` | — | Event handler called when the popup is opened or closed. |
| `itemToStringValue` | `(itemValue: Item) => string` | — | When the item values are objects (`<Combobox.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. With a `createItems()` collection, this receives the derived value. |
| `isItemEqualToValue` | `(itemValue: Item, value: Item) => boolean` | — | Custom comparison logic used to determine if a combobox item value matches the current selected value. Useful when item values are objects without matching referentially. With a `createItems()` collection, both arguments are derived values. Defaults to `Object.is` comparison. |
| `inputValue` | `string \| number \| readonly string[] \| undefined` | — | The input value of the combobox. Use when controlled. |
| `defaultInputValue` | `string \| number \| readonly string[] \| undefined` | — | The uncontrolled input value when initially rendered.<br>To render a controlled input, use the `inputValue` prop instead. |
| `onInputValueChange` | `(inputValue: string, eventDetails: ChangeEventDetails) => void` | — | Event handler called when the input value changes. |
| `itemToStringLabel` | `(itemValue: Item) => string` | — | When the item values are objects (`<Combobox.Item value={object}>`), this function converts the object value to a string representation for display in the input. If the shape of the object is `{ value, label }`, the label will be used automatically without needing to specify this prop. With a `createItems()` collection, this receives the derived value, and the collection's `getLabel` takes precedence for values it can resolve. |
| `autoHighlight` | `boolean` | `false` | Whether the first matching item is highlighted automatically while filtering. |
| `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<Actions \| null>` | — | A ref to imperative actions. - `unmount`: Manually unmounts the combobox. Call this after any externally controlled closing animation finishes. |
| `openOnInputClick` | `boolean` | `true` | Whether the popup opens when clicking the input. |
| `filteredItems` | `readonly Item[] \| readonly Group<Item>[] \| undefined` | — | Filtered items to display in the list. When provided, the list uses these items instead of filtering the `items` prop internally. When `items` is also provided, this array must preserve its flat or grouped structure. With a `createItems()` collection, pass source items rather than derived values. Nullish entries are not supported, as in `items`. Use when you want to control filtering logic externally with the `useFilter()` hook. |
| `onOpenChangeComplete` | `(open: boolean) => void` | — | Event handler called after any animations complete when the popup is opened or closed. |
| `loopFocus` | `boolean` | `true` | Whether to loop keyboard focus back to the input when the end of the list is reached while using the arrow keys. The first item can then be reached by pressing <kbd>ArrowDown</kbd> again from the input, or the last item can be reached by pressing <kbd>ArrowUp</kbd> from the input. The input is always included in the focus loop per [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/). When disabled, focus does not move when on the last element and the user presses <kbd>ArrowDown</kbd>, or when on the first element and the user presses <kbd>ArrowUp</kbd>. |
| `onItemHighlighted` | `(highlightedValue: Item \| undefined, eventDetails: HighlightEventDetails) => void` | — | Callback fired when an item is highlighted or unhighlighted. Receives the highlighted item value (or `undefined` if no item is highlighted) and event details with a `reason` property describing why the highlight changed. The `reason` can be: - `'keyboard'`: the highlight changed due to keyboard navigation. - `'pointer'`: the highlight changed due to pointer hovering. - `'none'`: the highlight changed programmatically. |
| `inputRef` | `Ref<HTMLInputElement> \| undefined` | — | A ref to the hidden input element. |
| `virtualized` | `boolean` | `false` | Whether the items are being externally virtualized. |
| `modal` | `boolean` | `false` | Determines if the popup enters a modal state when open. - `true`: user interaction is limited to the popup: 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. |
| `aria-describedby` | `string` | — | Identifies the element (or elements) that describes the object. |
| `aria-label` | `string` | — | Defines a string value that labels the current element. |
| `aria-labelledby` | `string` | — | Identifies the element (or elements) that labels the current element. |
| `popupWidth` | `"content" \| "anchor" \| "sm" \| "md" \| "lg" \| "available"` | `'anchor'` | Controls how the popup width is constrained relative to its anchor.<br>For all presets, the popup is never narrower than its anchor.<br>- `'anchor'`: Fixed width matching the anchor width. - `'content'`: Width grows with item labels between the anchor and available   viewport bounds. - `'sm'`: Fixed width at the small surface width token (`--wpds-dimension-surface-width-sm`). - `'md'`: Fixed width at the medium surface width token (`--wpds-dimension-surface-width-md`). - `'lg'`: Fixed width at the large surface width token (`--wpds-dimension-surface-width-lg`). - `'available'`: Fixed width at the available viewport width (`--available-width`). |
| `items` | `Item[] \| ItemGroup[] \| undefined` | — | The array of option items.<br>When using grouped `children`, pass an array of groups instead of a flat list of items. Grouped items require a custom `children` renderer.<br>Mark a creatable action with `creatable: true`. It renders in the list footer, not the main list, when it is in the filtered items. Handle the creation of the item in `onValueChange`. |
| `children` | `(item: any, index: number) => ReactNode` | — | A render function for custom rendering the list of matching items. Required when `items` contains groups. |
| `chipsContent` | `(value: Item[]) => ReactNode` | — | A render function for custom rendering the selected chips.<br>```jsx chipsContent={ ( value ) =>   value.map( ( item ) => (        ) ) } ``` |
| `emptyContent` | `ReactNode` | — | The custom content to use instead of the default empty state, which shows whenever there are no matching items. |
| `statusContent` | `ReactNode` | — | Content for the list status live region. The region stays mounted. |
| `searchPlaceholder` | `string` | — | The placeholder text to use for the search input. |
| `showClearButton` | `boolean` | `true` | Whether to show the clear button to remove all selected items. |
| `clearButtonLabel` | `string` | — | The aria-label for the clear button. |
| `label` *(required)* | `string` | — | The accessible label. All controls must be labeled. |
| `description` | `string` | — | The accessible description, associated using `aria-describedby`.<br>For screen reader accessibility, this should only contain plain text, and no semantics such as links. |
| `details` | `ReactNode` | — | Additional information about the field, which unlike a normal description, can include links and other semantic elements.<br>Do not use this prop when the content is only plain text; use `description` instead. |
| `hideLabelFromVision` | `boolean` | `false` | Whether to visually hide the label while keeping it accessible to screen readers. |
| `className` | `string` | — | CSS class to apply. |
| `ref` | `LegacyRef<HTMLInputElement> \| undefined` | — | Allows getting a ref to the component instance. Once the component unmounts, React will set `ref.current` to `null` (or call the ref with `null` if you passed a callback ref). |
| `key` | `Key \| null \| undefined` | — |  |


## Examples

### Default

```tsx
const Default = () => <SearchableChipSelectControl label="Label" description="This is a description." />;
```

### Visually Hidden Label

```tsx
const VisuallyHiddenLabel = () => <SearchableChipSelectControl hideLabelFromVision />;
```

### With Details

```tsx
const WithDetails = () => <SearchableChipSelectControl description={undefined} details={DETAILS_EXAMPLE} />;
```

### With Custom Search Placeholder

Use the `searchPlaceholder` prop to customize the search input placeholder
text. Prefer a concise label without a trailing ellipsis.

```tsx
const WithCustomSearchPlaceholder = () => <SearchableChipSelectControl searchPlaceholder="Search fruit" />;
```

### With Disabled Option

```tsx
const WithDisabledOption = () => <SearchableChipSelectControl items={disabledOptionItems} defaultValue={[ disabledOptionItems[ 0 ] ]} />;
```

### Creatable

Mark a creatable action with `creatable: true` on an item in `items`.
It renders in the list footer, not the main list, when it is in the
filtered items. Handle the creation of the item in `onValueChange`.

```tsx
const Creatable = function Template( args ) {
    const {
        items = ITEMS,
        value: initialValue = [ ITEMS[ 0 ], ITEMS[ 1 ] ],
        ...restArgs
    } = args;
    const [ inputValue, setInputValue ] = useState( '' );
    const [ value, setValue ] = useState( initialValue );
    const creatableItem = {
        value: '__create__',
        label:
            'Create new item' + ( inputValue ? `: ${ inputValue }` : '' ),
        creatable: true,
    };

    return (
        <SearchableChipSelectControl
            { ...restArgs }
            items={ [ ...( items as FixtureItem[] ), creatableItem ] }
            inputValue={ inputValue }
            onInputValueChange={ setInputValue }
            value={ value }
            onValueChange={ ( values, event ) => {
                if (
                    values.some(
                        ( item ) => item.value === creatableItem.value
                    )
                ) {
                    // eslint-disable-next-line no-alert
                    alert( `Create new item: '${ inputValue }'` );
                    setValue(
                        values.filter(
                            ( item ) => item.value !== creatableItem.value
                        )
                    );
                } else {
                    setValue( values );
                }
                args.onValueChange?.( values, event );
            } }
        />
    );
};
```

### With Custom Chips And Items

```tsx
const WithCustomChipsAndItems = () => <SearchableChipSelectControl />;
```

### With Custom Empty Content

```tsx
const WithCustomEmptyContent = () => <SearchableChipSelectControl />;
```

### Async Items

Loads the item list asynchronously. `statusContent` shows loading, then
a visually hidden result count. Pass `emptyContent={ null }` while
loading so Empty does not claim there are no results.

```tsx
const AsyncItems = () => {
    const [ loading, setLoading ] = useState( false );
    const [ items, setItems ] = useState< typeof ITEMS >( [] );
    const timeoutRef = useRef< ReturnType< typeof setTimeout > >();

    return (
        <SearchableChipSelectControl
            label="Label"
            description="This is a description."
            items={ items }
            statusContent={
                loading ? (
                    <Stack direction="row" gap="sm" align="center">
                        <Spinner />
                        Loading…
                    </Stack>
                ) : (
                    <HiddenResultCount />
                )
            }
            emptyContent={ loading ? null : undefined }
            onOpenChange={ ( open ) => {
                if ( ! open ) {
                    clearTimeout( timeoutRef.current );
                    return;
                }
                setLoading( true );
                setItems( [] );
                clearTimeout( timeoutRef.current );
                timeoutRef.current = setTimeout( () => {
                    setItems( ITEMS );
                    setLoading( false );
                }, 500 );
            } } />
    );
};
```

### Without Clear Button

```tsx
const WithoutClearButton = () => <SearchableChipSelectControl />;
```

### Grouped

Options can be organized into labeled groups with
`SearchableChipSelectControl.Group`, `SearchableChipSelectControl.GroupLabel`,
and `SearchableChipSelectControl.Collection`. Pass an array of groups to
`items` (each with `label` and `items` properties), and use `children` to
render each group.

```tsx
const Grouped = () => <SearchableChipSelectControl
    defaultValue={[
        GROUPED_ITEMS[ 0 ].items[ 0 ],
        GROUPED_ITEMS[ 1 ].items[ 0 ],
    ]}
    label="Fruit"
    description="Choose your favorite fruits." />;
```

### Grouped Creatable

Grouped items with a creatable footer item. Include the creatable item in
`items` as a creatable-only group. Handle the creation of the item in
`onValueChange`.

```tsx
const GroupedCreatable = () => {
    const [ inputValue, setInputValue ] = useState( '' );
    const [ value, setValue ] = useState< FixtureItem[] >( [
        GROUPED_ITEMS[ 0 ].items[ 0 ],
        GROUPED_ITEMS[ 1 ].items[ 0 ],
    ] );
    const creatableItem = {
        value: '__create__',
        label:
            'Create new item' + ( inputValue ? `: ${ inputValue }` : '' ),
        creatable: true,
    };
    const items = [
        ...GROUPED_ITEMS,
        { label: '', items: [ creatableItem ] },
    ];

    return (
        <SearchableChipSelectControl
            label="Fruit"
            description="Choose your favorite fruits."
            items={ items }
            inputValue={ inputValue }
            onInputValueChange={ setInputValue }
            value={ value }
            onValueChange={ ( values: FixtureItem[], event ) => {
                if (
                    values.some(
                        ( item ) => item.value === creatableItem.value
                    )
                ) {
                    // eslint-disable-next-line no-alert
                    alert( `Create new item: '${ inputValue }'` );
                    setValue(
                        values.filter(
                            ( item ) => item.value !== creatableItem.value
                        )
                    );
                } else {
                    setValue( values );
                }
                args.onValueChange?.( values, event );
            } }
            children={ ( group: FixtureGroup ) => (
                <SearchableChipSelectControl.Group
                    key={ group.label }
                    items={ group.items }
                >
                    <SearchableChipSelectControl.GroupLabel>
                        { group.label }
                    </SearchableChipSelectControl.GroupLabel>
                    <SearchableChipSelectControl.Collection>
                        { ( item: FixtureItem ) => (
                            <SearchableChipSelectControl.Item
                                key={ item.value }
                                value={ item }
                            >
                                { item.label }
                            </SearchableChipSelectControl.Item>
                        ) }
                    </SearchableChipSelectControl.Collection>
                </SearchableChipSelectControl.Group>
            ) } />
    );
};
```

### Popup Width

Use `popupWidth` to control how the popup width is constrained relative to
its anchor.

This example uses `sm`, allowing the popup to extend beyond the narrow anchor width.

```tsx
const PopupWidth = () => <SearchableChipSelectControl
    label="Tags"
    popupWidth="sm"
    items={longLabelPopupItems}
    value={[ longLabelPopupItems[ 0 ] ]} />;
```
