Beta Preview

ComboBox

ComboBox allow users to choose a single option from a collapsible list of options when space is limited.

 
 
size 
labelPosition 
 
contextualHelp 
isDisabled 
import {ComboBox, ComboBoxItem} from '@react-spectrum/s2';

<ComboBox
  label="Ice cream flavor"
  placeholder="Select a flavor">
  <ComboBoxItem>Chocolate</ComboBoxItem>
  <ComboBoxItem>Mint</ComboBoxItem>
  <ComboBoxItem>Strawberry</ComboBoxItem>
  <ComboBoxItem>Vanilla</ComboBoxItem>
  <ComboBoxItem>Chocolate Chip Cookie Dough</ComboBoxItem>
</ComboBox>

Content

ComboBox follows the Collection Components API, accepting both static and dynamic collections. This example shows a dynamic collection, passing a list of objects to the items prop, and a function to render the children.

import {ComboBox, ComboBoxItem} from '@react-spectrum/s2';

function Example() {
  let options = [
    { id: 1, name: 'Aardvark' },
    { id: 2, name: 'Cat' },
    { id: 3, name: 'Dog' },
    { id: 4, name: 'Kangaroo' },
    { id: 5, name: 'Koala' },
    { id: 6, name: 'Penguin' },
    { id: 7, name: 'Snake' },
    { id: 8, name: 'Turtle' },
    { id: 9, name: 'Wombat' }
  ];

  return (
    <ComboBox label="Animals" defaultItems={options} selectionMode="single" placeholder="Select an animal">
      {(item) => <ComboBoxItem>{item.name}</ComboBoxItem>}
    </ComboBox>
  );
}

Slots

ComboBoxItem supports icons, avatars, and label and description text slots.

import {Avatar, ComboBox, ComboBoxItem, Text} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import Comment from '@react-spectrum/s2/icons/Comment';
import Edit from '@react-spectrum/s2/icons/Edit';
import UserSettings from '@react-spectrum/s2/icons/UserSettings';

const users = Array.from({length: 5}, (_, i) => ({
  id: `user${i + 1}`,
  name: `User ${i + 1}`,
  email: `user${i + 1}@example.com`,
  avatar: 'https://i.imgur.com/kJOwAdv.png'
}));

<div className={style({display: 'flex', gap: 12, flexWrap: 'wrap'})}>
  <ComboBox label="Permissions" defaultSelectedKey="read" placeholder="Select a permission level">
    <ComboBoxItem id="read" textValue="Read">
      <Comment />
      <Text slot="label">Read</Text>
      <Text slot="description">Comment only</Text>
    </ComboBoxItem>
    <ComboBoxItem id="write" textValue="Write">
      <Edit />
      <Text slot="label">Write</Text>
      <Text slot="description">Read and write only</Text>
    </ComboBoxItem>
    <ComboBoxItem id="admin" textValue="Admin">
      <UserSettings />
      <Text slot="label">Admin</Text>
      <Text slot="description">Full access</Text>
    </ComboBoxItem>
  </ComboBox>
  <ComboBox label="Share" defaultItems={users} defaultSelectedKey="user1" placeholder="Select a user">
    {(item) => (
      <ComboBoxItem id={item.id} textValue={item.name}>
        <Avatar slot="avatar" src={item.avatar} />
        <Text slot="label">{item.name}</Text>
        <Text slot="description">{item.email}</Text>
      </ComboBoxItem>
    )}
  </ComboBox>
</div>

Sections

Use the <ComboBoxSection> component to group options. A <Header> element, with a <Heading> and optional description slot can be included to label a section. Sections without a header must have an aria-label.

import {ComboBox, ComboBoxItem, ComboBoxSection, Header, Heading, Text} from '@react-spectrum/s2';

<ComboBox label="Preferred fruit or vegetable" placeholder="Select an option">
  <ComboBoxSection>
    <Header>
      <Heading>Fruit</Heading>
      <Text slot="description">Sweet and nutritious</Text>
    </Header>
    <ComboBoxItem id="apple">Apple</ComboBoxItem>
    <ComboBoxItem id="banana">Banana</ComboBoxItem>
    <ComboBoxItem id="orange">Orange</ComboBoxItem>
    <ComboBoxItem id="grapes">Grapes</ComboBoxItem>
  </ComboBoxSection>
  <ComboBoxSection>
    <Header>
      <Heading>Vegetable</Heading>
      <Text slot="description">Healthy and savory</Text>
    </Header>
    <ComboBoxItem id="broccoli">Broccoli</ComboBoxItem>
    <ComboBoxItem id="carrots">Carrots</ComboBoxItem>
    <ComboBoxItem id="spinach">Spinach</ComboBoxItem>
    <ComboBoxItem id="lettuce">Lettuce</ComboBoxItem>
  </ComboBoxSection>
</ComboBox>

Asynchronous loading

Use the loadingState and onLoadMore props to enable async loading and infinite scrolling.

import {ComboBox, ComboBoxItem} from '@react-spectrum/s2';
import {useAsyncList} from 'react-stately';

function Example() {
  let list = useAsyncList({
    async load({signal, cursor, filterText}) {
      if (cursor) {
        cursor = cursor.replace(/^http:\/\//i, 'https://');
      }

      let res = await fetch(cursor || `https://swapi.py4e.com/api/people/?search=${filterText}`, {signal});
      let json = await res.json();

      return {
        items: json.results,
        cursor: json.next
      };
    }
  });

  return (
    <ComboBox
      label="Star Wars Character Lookup"
      placeholder="Select a character"
      items={list.items}
      inputValue={list.filterText}
      onInputChange={list.setFilterText}
      loadingState={list.loadingState}
      onLoadMore={list.loadMore}>
      {item => <ComboBoxItem id={item.name} textValue={item.name}>{item.name}</ComboBoxItem>}
    </ComboBox>
  );
}

Actions

Use the onAction prop on a <ComboBoxItem> to perform a custom action when the item is pressed. This example adds a "Create" action for the current input value.

import {ComboBox, ComboBoxItem} from '@react-spectrum/s2';
import {useState} from 'react';

function Example() {
  let [inputValue, setInputValue] = useState('');

  return (
    <ComboBox
      label="Favorite Animal"
      placeholder="Select an animal"
      inputValue={inputValue}
      onInputChange={setInputValue}>
      {inputValue.length > 0 && (
        <ComboBoxItem onAction={() => alert('Creating ' + inputValue)}>
          {`Create "${inputValue}"`}
        </ComboBoxItem>
      )}
      <ComboBoxItem>Aardvark</ComboBoxItem>
      <ComboBoxItem>Cat</ComboBoxItem>
      <ComboBoxItem>Dog</ComboBoxItem>
      <ComboBoxItem>Kangaroo</ComboBoxItem>
      <ComboBoxItem>Panda</ComboBoxItem>
      <ComboBoxItem>Snake</ComboBoxItem>
    </ComboBox>
  );
}

Use the href prop on a <ComboBoxItem> to create a link. See the client side routing guide to learn how to integrate with your framework. Link items in a ComboBox are not selectable.

import {ComboBox, ComboBoxItem} from '@react-spectrum/s2';

<ComboBox label="Bookmarks" placeholder="Select a bookmark">
  <ComboBoxItem href="https://adobe.com/" target="_blank">Adobe</ComboBoxItem>
  <ComboBoxItem href="https://apple.com/" target="_blank">Apple</ComboBoxItem>
  <ComboBoxItem href="https://google.com/" target="_blank">Google</ComboBoxItem>
  <ComboBoxItem href="https://microsoft.com/" target="_blank">Microsoft</ComboBoxItem>
</ComboBox>

Selection

Use the defaultSelectedKey or selectedKey prop to set the selected item. The selected key corresponds to the id prop of an item. Items can be disabled with the isDisabled prop. See the selection guide for more details.

Current selection: bison

import {ComboBox, ComboBoxItem, type Key} from '@react-spectrum/s2';
import {useState} from 'react';

function Example() {
  let [animal, setAnimal] = useState<Key>("bison");

  return (
    <div>
      <ComboBox
        label="Pick an animal"
        placeholder="Select an animal"
        selectedKey={animal}
        onSelectionChange={setAnimal}>
        <ComboBoxItem id="koala">Koala</ComboBoxItem>
        <ComboBoxItem id="kangaroo">Kangaroo</ComboBoxItem>
        <ComboBoxItem id="platypus" isDisabled>Platypus</ComboBoxItem>
        <ComboBoxItem id="eagle">Bald Eagle</ComboBoxItem>
        <ComboBoxItem id="bison">Bison</ComboBoxItem>
        <ComboBoxItem id="skunk">Skunk</ComboBoxItem>
      </ComboBox>
      <p>Current selection: {animal}</p>
    </div>
  );
}

Input value

Use the inputValue or defaultInputValue prop to set the value of the input field. By default, the value will be reverted to the selected item on blur. Set the allowsCustomValue prop to enable entering values that are not in the list.

Current input value: Kangaroo

allowsCustomValue 
import {ComboBox, ComboBoxItem, type Key} from '@react-spectrum/s2';
import {useState} from 'react';

function Example(props) {
  let [value, setValue] = useState<Key>('Kangaroo');

  return (
    <div>
      <ComboBox
        {...props}
        label="Favorite Animal"
        placeholder="Select an animal"
        defaultSelectedKey="kangaroo"
        inputValue={value}
        onInputChange={setValue}>
        <ComboBoxItem id="koala">Koala</ComboBoxItem>
        <ComboBoxItem id="kangaroo">Kangaroo</ComboBoxItem>
        <ComboBoxItem id="platypus">Platypus</ComboBoxItem>
        <ComboBoxItem id="eagle">Bald Eagle</ComboBoxItem>
        <ComboBoxItem id="bison">Bison</ComboBoxItem>
        <ComboBoxItem id="skunk">Skunk</ComboBoxItem>
      </ComboBox>
      <p>Current input value: {value}</p>
    </div>
  );
}

Fully controlled

Both inputValue and selectedKey can be controlled simultaneously. However, each interaction will only trigger either onInputChange or onSelectionChange, not both. When controlling both props, you must update both values accordingly.

Current selected major id: 
Current input text: 
import {ComboBox, ComboBoxItem, type Key} from '@react-spectrum/s2';
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
import {useState} from 'react';

function ControlledComboBox() {
let [fieldState, setFieldState] = useState({ selectedKey: null, inputValue: '' }); let onSelectionChange = (id: Key) => { // Update inputValue when selectedKey changes. setFieldState({ inputValue: options.find(o => o.id === id)?.name ?? '', selectedKey: id }); }; let onInputChange = (value: string) => { // Reset selectedKey to null if the input is cleared. setFieldState(prevState => ({ inputValue: value, selectedKey: value === '' ? null : prevState.selectedKey })); }; return ( <div> <ComboBox label="Pick a engineering major"
placeholder="Select a major" defaultItems={options} selectedKey={fieldState.selectedKey} inputValue={fieldState.inputValue} onSelectionChange={onSelectionChange} onInputChange={onInputChange}> {item => <ComboBoxItem>{item.name}</ComboBoxItem>} </ComboBox> <pre className={style({font: 'body'})}> Current selected major id: {fieldState.selectedKey}{'\n'} Current input text: {fieldState.inputValue} </pre> </div> ); }

Forms

Use the name prop to submit the id of the selected item to the server. Set the isRequired prop to validate that the user selects a value, or implement custom client or server-side validation. See the Forms guide to learn more.

Please select an animal.
isRequired 
necessityIndicator 
import {ComboBox, ComboBoxItem, Form, Button} from '@react-spectrum/s2';

function Example(props) {
  return (
    <Form>
      <ComboBox
        {...props}
        label="Animal"
        placeholder="e.g. Cat"
        name="animal"
        isRequired
        description="Please select an animal.">
        <ComboBoxItem id="aardvark">Aardvark</ComboBoxItem>
        <ComboBoxItem id="cat">Cat</ComboBoxItem>
        <ComboBoxItem id="dog">Dog</ComboBoxItem>
        <ComboBoxItem id="kangaroo">Kangaroo</ComboBoxItem>
        <ComboBoxItem id="panda">Panda</ComboBoxItem>
        <ComboBoxItem id="snake">Snake</ComboBoxItem>
      </ComboBox>
      <Button type="submit">Submit</Button>
    </Form>
  );
}

Popover options

Use the menuTrigger prop to control when the popover opens:

  • input (default): popover opens when the user edits the input text.
  • focus: popover opens when the user focuses the input.
  • manual: popover only opens when the user presses the trigger button or uses the arrow keys.

The align, direction, shouldFlip and menuWidth props control the behavior of the popover.

menuTrigger 
align 
direction 
shouldFlip 
 
import {ComboBox, ComboBoxItem} from '@react-spectrum/s2';

<ComboBox
  label="Favorite Animal"
  placeholder="Select an animal">
  <ComboBoxItem id="red panda">Red Panda</ComboBoxItem>
  <ComboBoxItem id="cat">Cat</ComboBoxItem>
  <ComboBoxItem id="dog">Dog</ComboBoxItem>
  <ComboBoxItem id="aardvark">Aardvark</ComboBoxItem>
  <ComboBoxItem id="kangaroo">Kangaroo</ComboBoxItem>
  <ComboBoxItem id="snake">Snake</ComboBoxItem>
</ComboBox>

API

<ComboBox>
  <ComboBoxItem>
    <Icon />
    <Text slot="label" />
    <Text slot="description" />
  </ComboBoxItem>
  <ComboBoxSection>
    <Header>
      <Heading />
      <Text slot="description" />
    </Header>
    <ComboBoxItem />
  </ComboBoxSection>
</ComboBox>

ComboBox

NameTypeDefault
allowsCustomValuebooleanDefault:
Whether the ComboBox allows a non-item matching input value to be set.
menuTriggerDefault: 'input'
The interaction required to display the ComboBox menu.
isDisabledbooleanDefault:
Whether the input is disabled.
isReadOnlybooleanDefault:
Whether the input can be selected but not changed by the user.
size'S''M''L''XL'Default: 'M'
The size of the Combobox.
stylesDefault:
Spectrum-defined styles, returned by the style() macro.
placeholderstringDefault:
Temporary text that occupies the text input when it is empty. See MDN.
childrenReactNode(item: object) => ReactNodeDefault:
The contents of the collection.
itemsIterable<T>Default:
The list of ComboBox items (controlled).
defaultItemsIterable<T>Default:
The list of ComboBox items (uncontrolled).
loadingStateDefault:
The current loading state of the ComboBox. Determines whether or not the progress circle should be shown.
onLoadMore() => anyDefault:
Handler that is called when more items should be loaded, e.g. while scrolling near the bottom.
dependenciesReadonlyArray<any>Default:
Values that should invalidate the item cache when using dynamic collections.
selectedKeyKeynullDefault:
The currently selected key in the collection (controlled).
defaultSelectedKeyKeyDefault:
The initial selected key in the collection (uncontrolled).
onSelectionChange(key: Keynull) => voidDefault:
Handler that is called when the selection changes.
disabledKeysIterable<Key>Default:
The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with.
shouldFocusWrapbooleanDefault:
Whether keyboard navigation is circular.
inputValuestringDefault:
The value of the ComboBox input (controlled).
defaultInputValuestringDefault:
The default value of the ComboBox input (uncontrolled).
onInputChange(value: string) => voidDefault:
Handler that is called when the ComboBox input value changes.

ComboBoxItem

NameType
childrenReactNode
idKey
The unique id of the item.
valueT
The object value that this item represents. When using dynamic collections, this is set automatically.
textValuestring
A string representation of the item's contents, used for features like typeahead.
isDisabledboolean
Whether the item is disabled.
styles
Spectrum-defined styles, returned by the style() macro.

ComboBoxSection

NameType
idKey
The unique id of the section.
valueT
The object value that this section represents. When using dynamic collections, this is set automatically.
childrenReactNode(item: T) => ReactElement
Static child items or a function to render children.
itemsIterable<T>
Item objects in the section.
dependenciesReadonlyArray<any>
Values that should invalidate the item cache when using dynamic collections.

Testing

Test utils

@react-spectrum/test-utils offers common combobox interaction utilities which you may find helpful when writing tests. To install, simply add it to your dev dependencies via your preferred package manager.

yarn add @react-spectrum/test-utils --dev

Once installed, you can access the User that @react-spectrum/test-utils provides in your test file as shown below. This user only needs to be initialized once and then can be used to generate the ComboBox tester in your test cases. This gives you access to ComboBox specific utilities that you can then call within your test to query for specific subcomponents or simulate common interactions. The example test case below shows how you might go about setting up the ComboBox tester, use it to simulate option selection, and verify the combobox's state after each interaction.

// Combobox.test.ts
import {render} from '@testing-library/react';
import {User} from '@react-spectrum/test-utils';

let testUtilUser = new User({interactionType: 'mouse'});
// ...

it('ComboBox can select an option via keyboard', async function () {
  // Render your test component/app and initialize the combobox tester
  let {getByTestId} = render(
    <ComboBox data-testid="test-combobox">
      ...
    </ComboBox>
  );
  let comboboxTester = testUtilUser.createTester('ComboBox', {root: getByTestId('test-combobox'), interactionType: 'keyboard'});

  await comboboxTester.open();
  expect(comboboxTester.listbox).toBeInTheDocument();

  let options = comboboxTester.options();
  await comboboxTester.selectOption({option: options[0]});
  expect(comboboxTester.combobox.value).toBe('One');
  expect(comboboxTester.listbox).not.toBeInTheDocument();
});

See below for the full definition of the User and the ComboBox tester.

Properties

NameTypeDefault
advanceTimer['advanceTimer']Default:
A function used by the test utils to advance timers during interactions. Required for certain aria patterns (e.g. table).
interactionType['interactionType']Default: mouse
The interaction type (mouse, touch, keyboard) that the test util user will use when interacting with a component. This can be overridden at the aria pattern util level if needed.

Methods

constructor(opts: ): void
createTester<T extends >(patternName: T, opts: <T>): <T>
Creates an aria pattern tester, inheriting the options provided to the original user.

Properties

NameType
focusedOptionHTMLElementnull
Returns the currently focused option in the combobox's dropdown if any.
sectionsHTMLElement[]
Returns the combobox's sections if present.
listboxHTMLElementnull
Returns the combobox's listbox if present.
triggerHTMLElement
Returns the combobox trigger button.
comboboxHTMLElement
Returns the combobox.

Methods

constructor(opts: ): void
setInteractionType(type: ['interactionType']): void
Set the interaction type used by the combobox tester.
open(opts: ): Promise<void>
Opens the combobox dropdown. Defaults to using the interaction type set on the combobox tester.
findOption(opts: {
optionIndexOrText: numberstring
}): HTMLElement
Returns an option matching the specified index or text content.
selectOption(opts: ): Promise<void>
Selects the desired combobox option. Defaults to using the interaction type set on the combobox tester. If necessary, will open the combobox dropdown beforehand. The desired option can be targeted via the option's node, the option's text, or the option's index.
close(): Promise<void>
Closes the combobox dropdown.
options(opts: {
element?: HTMLElement
}): HTMLElement[]
Returns the combobox's options if present. Can be filtered to a subsection of the listbox if provided via element.

Testing FAQ