ListBox

A listbox displays a list of options and allows a user to select one or more of them.

installyarn add react-aria-components
version3.17.0
usageimport {ListBox} from 'react-aria-components'

Example#


import {ListBox, Item} from 'react-aria-components';

<ListBox aria-label="Favorite animal" selectionMode="single">
  <Item>Aardvark</Item>
  <Item>Cat</Item>
  <Item>Dog</Item>
  <Item>Kangaroo</Item>
  <Item>Panda</Item>
  <Item>Snake</Item>
</ListBox>
import {Item, ListBox} from 'react-aria-components';

<ListBox
  aria-label="Favorite animal"
  selectionMode="single"
>
  <Item>Aardvark</Item>
  <Item>Cat</Item>
  <Item>Dog</Item>
  <Item>Kangaroo</Item>
  <Item>Panda</Item>
  <Item>Snake</Item>
</ListBox>
import {
  Item,
  ListBox
} from 'react-aria-components';

<ListBox
  aria-label="Favorite animal"
  selectionMode="single"
>
  <Item>Aardvark</Item>
  <Item>Cat</Item>
  <Item>Dog</Item>
  <Item>Kangaroo</Item>
  <Item>Panda</Item>
  <Item>Snake</Item>
</ListBox>
Show CSS
.react-aria-ListBox {
  max-height: inherit;
  overflow: auto;
  padding: 2px;
  border: 1px solid var(--spectrum-global-color-gray-400);
  border-radius: 6px;
  background: var(--page-background);
  outline: none;
  min-width: var(--button-width);
  max-width: 250px;
  max-height: 300px;
  box-sizing: border-box;

  & section:not(:first-child) {
    margin-top: 12px;
  }

  & section header {
    font-size: 16px;
    font-weight: bold;
    padding: 0 8px;
  }

  & .react-aria-Item {
    margin: 2px;
    padding: 4px 8px 4px 8px;
    border-radius: 6px;
    outline: none;
    cursor: default;
    color: var(--spectrum-global-color-gray-800);
    font-size: 1.072rem;
    position: relative;
    display: flex;
    flex-direction: column;

    &[data-focus-visible] {
      box-shadow: inset 0 0 0 2px slateblue;
    }

    &[aria-selected=true] {
      background: slateblue;
      color: white;

      &[data-focus-visible] {
        box-shadow: inset 0 0 0 2px slateblue, inset 0 0 0 4px white;
      }
    }

    &[aria-disabled] {
      opacity: 0.4;
    }

    & [slot=label] {
      font-weight: bold;
    }

    & [slot=description] {
      font-size: small;
    }
  }
}
.react-aria-ListBox {
  max-height: inherit;
  overflow: auto;
  padding: 2px;
  border: 1px solid var(--spectrum-global-color-gray-400);
  border-radius: 6px;
  background: var(--page-background);
  outline: none;
  min-width: var(--button-width);
  max-width: 250px;
  max-height: 300px;
  box-sizing: border-box;

  & section:not(:first-child) {
    margin-top: 12px;
  }

  & section header {
    font-size: 16px;
    font-weight: bold;
    padding: 0 8px;
  }

  & .react-aria-Item {
    margin: 2px;
    padding: 4px 8px 4px 8px;
    border-radius: 6px;
    outline: none;
    cursor: default;
    color: var(--spectrum-global-color-gray-800);
    font-size: 1.072rem;
    position: relative;
    display: flex;
    flex-direction: column;

    &[data-focus-visible] {
      box-shadow: inset 0 0 0 2px slateblue;
    }

    &[aria-selected=true] {
      background: slateblue;
      color: white;

      &[data-focus-visible] {
        box-shadow: inset 0 0 0 2px slateblue, inset 0 0 0 4px white;
      }
    }

    &[aria-disabled] {
      opacity: 0.4;
    }

    & [slot=label] {
      font-weight: bold;
    }

    & [slot=description] {
      font-size: small;
    }
  }
}
.react-aria-ListBox {
  max-height: inherit;
  overflow: auto;
  padding: 2px;
  border: 1px solid var(--spectrum-global-color-gray-400);
  border-radius: 6px;
  background: var(--page-background);
  outline: none;
  min-width: var(--button-width);
  max-width: 250px;
  max-height: 300px;
  box-sizing: border-box;

  & section:not(:first-child) {
    margin-top: 12px;
  }

  & section header {
    font-size: 16px;
    font-weight: bold;
    padding: 0 8px;
  }

  & .react-aria-Item {
    margin: 2px;
    padding: 4px 8px 4px 8px;
    border-radius: 6px;
    outline: none;
    cursor: default;
    color: var(--spectrum-global-color-gray-800);
    font-size: 1.072rem;
    position: relative;
    display: flex;
    flex-direction: column;

    &[data-focus-visible] {
      box-shadow: inset 0 0 0 2px slateblue;
    }

    &[aria-selected=true] {
      background: slateblue;
      color: white;

      &[data-focus-visible] {
        box-shadow: inset 0 0 0 2px slateblue, inset 0 0 0 4px white;
      }
    }

    &[aria-disabled] {
      opacity: 0.4;
    }

    & [slot=label] {
      font-weight: bold;
    }

    & [slot=description] {
      font-size: small;
    }
  }
}

Features#


A listbox can be built using the <select> and <option> HTML elements, but this is not possible to style consistently cross browser. ListBox helps you build accessible listbox components that can be styled as needed.

  • Item selection – Single or multiple selection, disabled rows, and both toggle and replace selection behaviors.
  • Keyboard navigation – List items can be navigated using the arrow keys, along with page up/down, home/end, etc. Typeahead, auto scrolling, and selection modifier keys are supported as well.
  • Touch friendly – Selection behavior adapts depending on the device. For example, selection occurs on mouse down but on touch up, which is consistent with native conventions.
  • Accessible – Follows the ARIA listbox pattern, with support for items and sections, and slots for label and description elements within each item for improved screen reader announcement.
  • Styleable – Items include builtin states for styling, such as hover, press, focus, selected, and disabled.

Note: ListBox only handles the list itself. For a dropdown, see Select.

Anatomy#


LabelOption 1Option 2OptionLabelOption labelDescriptionDescriptionOption 3DescriptionOption descriptionListboxSECTION TITLESection headingGroup

A listbox consists of a container element, with a list of options or groups inside. Users can select one or more options by clicking, tapping, or navigating with the keyboard.

Concepts#

ListBox makes use of the following concepts:

Props#


ListBox#

NameTypeDefaultDescription
selectionBehaviorSelectionBehaviorHow multiple selection should behave in the collection.
autoFocusbooleanFocusStrategyWhether to auto focus the listbox or an option.
shouldFocusWrapbooleanWhether focus should wrap around when the end/start is reached.
itemsIterable<T>Item objects in the collection.
disabledKeysIterable<Key>The item keys that are disabled. These items cannot be selected, focused, or otherwise interacted with.
selectionModeSelectionModeThe type of selection that is allowed in the collection.
disallowEmptySelectionbooleanWhether the collection allows empty selection.
selectedKeys'all'Iterable<Key>The currently selected keys in the collection (controlled).
defaultSelectedKeys'all'Iterable<Key>The initial selected keys in the collection (uncontrolled).
childrenReactNode( (item: T )) => ReactElement
classNamestring
styleCSSProperties
Events
NameTypeDefaultDescription
onSelectionChange( (keys: Selection )) => anyHandler that is called when the selection changes.
onFocus( (e: FocusEvent )) => voidHandler that is called when the element receives focus.
onBlur( (e: FocusEvent )) => voidHandler that is called when the element loses focus.
onFocusChange( (isFocused: boolean )) => voidHandler that is called when the element's focus status changes.
Accessibility
NameTypeDefaultDescription
idstringThe element's unique identifier. See MDN.
aria-labelstringDefines a string value that labels the current element.
aria-labelledbystringIdentifies the element (or elements) that labels the current element.
aria-describedbystringIdentifies the element (or elements) that describes the object.
aria-detailsstringIdentifies the element (or elements) that provide a detailed, extended description for the object.

Section#

A <Section> defines the child items, and optional title for a section within a <ListBox>.

Show props
NameTypeDefaultDescription
titleReactNodeRendered contents of the section, e.g. a header.
itemsIterable<T>Item objects in the section.
childrenReactNode
classNamestring
styleCSSProperties
Accessibility
NameTypeDefaultDescription
aria-labelstringAn accessibility label for the section.

Item#

An <Item> defines a single option within a <ListBox>. If the children are not plain text, then the textValue prop must also be set to a plain text representation, which will be used for typeahead in the ListBox.

Show props
NameTypeDefaultDescription
titleReactNodeRendered contents of the item if children contains child items.
textValuestringA string representation of the item's contents, used for features like typeahead.
childItemsIterable<T>A list of child item objects. Used for dynamic collections.
hasChildItemsbooleanWhether this item has children, even if not loaded yet.
childrenReactNode( (values: ItemStates )) => ReactNode
classNamestring( (values: ItemStates )) => string
styleCSSProperties( (values: ItemStates )) => CSSProperties
Accessibility
NameTypeDefaultDescription
aria-labelstringAn accessibility label for this item.

Styling#


React Aria components can be styled in many ways, including using CSS classes, inline styles, utility classes (e.g. Tailwind), CSS-in-JS (e.g. Styled Components), etc. By default, all components include a builtin className attribute which can be targeted using CSS selectors. These follow the react-aria-ComponentName naming convention.

.react-aria-ListBox {
  /* ... */
}
.react-aria-ListBox {
  /* ... */
}
.react-aria-ListBox {
  /* ... */
}

A custom className can also be specified on any component. This overrides the default className provided by React Aria with your own.

<ListBox className="my-listbox">
  {/* ... */}
</ListBox>
<ListBox className="my-listbox">
  {/* ... */}
</ListBox>
<ListBox className="my-listbox">
  {/* ... */}
</ListBox>;

In addition, some components support multiple UI states (e.g. pressed, hovered, etc.). React Aria components expose states using DOM attributes, which you can target in CSS selectors. These are ARIA attributes wherever possible, or data attributes when a relevant ARIA attribute does not exist. For example:

.react-aria-Item[aria-selected=true] {
  /* ... */
}

.react-aria-Item[data-focused] {
  /* ... */
}
.react-aria-Item[aria-selected=true] {
  /* ... */
}

.react-aria-Item[data-focused] {
  /* ... */
}
.react-aria-Item[aria-selected=true] {
  /* ... */
}

.react-aria-Item[data-focused] {
  /* ... */
}

The className and style props also accept functions which receive states for styling. This lets you dynamically determine the classes or styles to apply, which is useful when using utility CSS libraries like Tailwind.

<Item className={({isSelected}) => isSelected ? 'bg-blue-400' : 'bg-gray-100'}>
  Item
</Item>
<Item
  className={({ isSelected }) =>
    isSelected ? 'bg-blue-400' : 'bg-gray-100'}
>
  Item
</Item>;
<Item
  className={(
    { isSelected }
  ) =>
    isSelected
      ? 'bg-blue-400'
      : 'bg-gray-100'}
>
  Item
</Item>;

Render props may also be used as children to alter what elements are rendered based on the current state. For example, you could render a checkmark icon when an item is selected.

<Item>
  {({isSelected}) => (
    <>
      {isSelected && <CheckmarkIcon />}
      Item
    </>
  )}
</Item>
<Item>
  {({isSelected}) => (
    <>
      {isSelected && <CheckmarkIcon />}
      Item
    </>
  )}
</Item>
<Item>
  {({ isSelected }) => (
    <>
      {isSelected && (
        <CheckmarkIcon />
      )}
      Item
    </>
  )}
</Item>;

The states and selectors for each component used in a ListBox are documented below.

ListBox#

A ListBox can be targeted with the .react-aria-ListBox CSS selector, or by overriding with a custom className.

Section#

A Section can be targeted with the .react-aria-Section CSS selector, or by overriding with a custom className. The section title can be targeted with the header selector. The title prop also allows JSX elements and not just strings, which can enable custom formatting. However, keep in mind that interactive elements within a listbox are not allowed. See sections for examples.

Item#

An Item can be targeted with the .react-aria-Item CSS selector, or by overriding with a custom className. It supports the following states and render props:

NameCSS SelectorDescription
isHovered[data-hovered]Whether the item is currently hovered with a mouse.
isPressed[data-pressed]Whether the item is currently in a pressed state.
isSelected[aria-selected=true]Whether the item is currently selected.
isFocused[data-focused]Whether the item is currently focused.
isFocusVisible[data-focus-visible]Whether the item is currently keyboard focused.
isDisabled[aria-disabled]

Whether the item is non-interactive, i.e. both selection and actions are disabled and the item may not be focused. Dependent on disabledKeys and disabledBehavior.

selectionModeThe type of selection that is allowed in the collection.
selectionBehaviorThe selection behavior for the collection.

Items also support two slots: a label, and a description. When provided using the <Text> element, the item will have aria-labelledby and aria-describedby attributes pointing to these slots, improving screen reader announcement. See complex items for an example.

Note that items may not contain interactive children such as buttons, as screen readers will not be able to access them.

Reusable wrappers#


If you will use a ListBox in multiple places in your app, you can wrap all of the pieces into a reusable component. This way, the DOM structure, styling code, and other logic are defined in a single place and reused everywhere to ensure consistency.

This example wraps ListBox and all of its children together into a single component which accepts a label prop and children, which are passed through to the right places. The Item component is also wrapped to apply class names based on the current state, as described above.

function MyListBox({ children, ...props }) {
  return (
    <ListBox {...props} className="my-listbox">
      {children}
    </ListBox>
  );
}

function MyItem(props) {
  return (
    <Item
      {...props}
      className={({ isFocusVisible, isSelected }) =>
        `my-item ${isFocusVisible ? 'focused' : ''} ${
          isSelected ? 'selected' : ''
        }`}
    />
  );
}

<MyListBox aria-label="Ice cream flavor" selectionMode="single">
  <MyItem>Chocolate</MyItem>
  <MyItem>Mint</MyItem>
  <MyItem>Strawberry</MyItem>
  <MyItem>Vanilla</MyItem>
</MyListBox>
function MyListBox({ children, ...props }) {
  return (
    <ListBox {...props} className="my-listbox">
      {children}
    </ListBox>
  );
}

function MyItem(props) {
  return (
    <Item
      {...props}
      className={({ isFocusVisible, isSelected }) =>
        `my-item ${isFocusVisible ? 'focused' : ''} ${
          isSelected ? 'selected' : ''
        }`}
    />
  );
}

<MyListBox
  aria-label="Ice cream flavor"
  selectionMode="single"
>
  <MyItem>Chocolate</MyItem>
  <MyItem>Mint</MyItem>
  <MyItem>Strawberry</MyItem>
  <MyItem>Vanilla</MyItem>
</MyListBox>
function MyListBox(
  { children, ...props }
) {
  return (
    <ListBox
      {...props}
      className="my-listbox"
    >
      {children}
    </ListBox>
  );
}

function MyItem(props) {
  return (
    <Item
      {...props}
      className={(
        {
          isFocusVisible,
          isSelected
        }
      ) =>
        `my-item ${
          isFocusVisible
            ? 'focused'
            : ''
        } ${
          isSelected
            ? 'selected'
            : ''
        }`}
    />
  );
}

<MyListBox
  aria-label="Ice cream flavor"
  selectionMode="single"
>
  <MyItem>
    Chocolate
  </MyItem>
  <MyItem>Mint</MyItem>
  <MyItem>
    Strawberry
  </MyItem>
  <MyItem>
    Vanilla
  </MyItem>
</MyListBox>
Show CSS
.my-listbox {
  max-height: inherit;
  overflow: auto;
  padding: 2px;
  border: 1px solid var(--spectrum-global-color-gray-400);
  border-radius: 6px;
  background: var(--page-background);
  outline: none;
  min-width: var(--button-width);
  max-width: 250px;
  max-height: 300px;
  box-sizing: border-box;
}

.my-item {
  margin: 2px;
  padding: 4px 8px 4px 8px;
  border-radius: 6px;
  outline: none;
  cursor: default;
  color: var(--spectrum-global-color-gray-800);
  font-size: 1.072rem;

  &.selected {
    background: #e70073;
    color: white;
  }

  &.focused {
    box-shadow: 0 0 0 2px var(--page-background), 0 0 0 4px #e70073;
  }
}
.my-listbox {
  max-height: inherit;
  overflow: auto;
  padding: 2px;
  border: 1px solid var(--spectrum-global-color-gray-400);
  border-radius: 6px;
  background: var(--page-background);
  outline: none;
  min-width: var(--button-width);
  max-width: 250px;
  max-height: 300px;
  box-sizing: border-box;
}

.my-item {
  margin: 2px;
  padding: 4px 8px 4px 8px;
  border-radius: 6px;
  outline: none;
  cursor: default;
  color: var(--spectrum-global-color-gray-800);
  font-size: 1.072rem;

  &.selected {
    background: #e70073;
    color: white;
  }

  &.focused {
    box-shadow: 0 0 0 2px var(--page-background), 0 0 0 4px #e70073;
  }
}
.my-listbox {
  max-height: inherit;
  overflow: auto;
  padding: 2px;
  border: 1px solid var(--spectrum-global-color-gray-400);
  border-radius: 6px;
  background: var(--page-background);
  outline: none;
  min-width: var(--button-width);
  max-width: 250px;
  max-height: 300px;
  box-sizing: border-box;
}

.my-item {
  margin: 2px;
  padding: 4px 8px 4px 8px;
  border-radius: 6px;
  outline: none;
  cursor: default;
  color: var(--spectrum-global-color-gray-800);
  font-size: 1.072rem;

  &.selected {
    background: #e70073;
    color: white;
  }

  &.focused {
    box-shadow: 0 0 0 2px var(--page-background), 0 0 0 4px #e70073;
  }
}

Usage#


Dynamic collections#

ListBox follows the Collection Components API, accepting both static and dynamic collections. The examples above show static collections, which can be used when the full list of options is known ahead of time. Dynamic collections, as shown below, can be used when the options come from an external data source such as an API call, or update over time.

As seen below, an iterable list of options is passed to the ListBox using the items prop. Each item accepts an id prop, which is passed to the onSelectionChange handler to identify the selected item. Alternatively, if the item objects contain an id property, as shown in the example below, then this is used automatically and an id prop is not required.

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 (
    <ListBox aria-label="Animals" items={options} selectionMode="single">
      {(item) => <Item>{item.name}</Item>}
    </ListBox>
  );
}
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 (
    <ListBox
      aria-label="Animals"
      items={options}
      selectionMode="single"
    >
      {(item) => <Item>{item.name}</Item>}
    </ListBox>
  );
}
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 (
    <ListBox
      aria-label="Animals"
      items={options}
      selectionMode="single"
    >
      {(item) => (
        <Item>
          {item.name}
        </Item>
      )}
    </ListBox>
  );
}

Selection#

ListBox supports multiple selection modes. By default, selection is disabled, however this can be changed using the selectionMode prop. Use defaultSelectedKeys to provide a default set of selected items (uncontrolled) and selectedKeys to set the selected items (controlled). The value of the selected keys must match the id prop of the items. See the react-stately Selection docs for more details.

function Example() {
  let [selected, setSelected] = React.useState(new Set(['cheese']));

  return (
    <>
      <ListBox
        aria-label="Sandwich contents"
        selectionMode="multiple"
        selectedKeys={selected}
        onSelectionChange={setSelected}
      >
        <Item id="lettuce">Lettuce</Item>
        <Item id="tomato">Tomato</Item>
        <Item id="cheese">Cheese</Item>
        <Item id="tuna">Tuna Salad</Item>
        <Item id="egg">Egg Salad</Item>
        <Item id="ham">Ham</Item>
      </ListBox>
      <p>Current selection (controlled): {[...selected].join(', ')}</p>
    </>
  );
}
function Example() {
  let [selected, setSelected] = React.useState(
    new Set(['cheese'])
  );

  return (
    <>
      <ListBox
        aria-label="Sandwich contents"
        selectionMode="multiple"
        selectedKeys={selected}
        onSelectionChange={setSelected}
      >
        <Item id="lettuce">Lettuce</Item>
        <Item id="tomato">Tomato</Item>
        <Item id="cheese">Cheese</Item>
        <Item id="tuna">Tuna Salad</Item>
        <Item id="egg">Egg Salad</Item>
        <Item id="ham">Ham</Item>
      </ListBox>
      <p>
        Current selection (controlled):{' '}
        {[...selected].join(', ')}
      </p>
    </>
  );
}
function Example() {
  let [
    selected,
    setSelected
  ] = React.useState(
    new Set(['cheese'])
  );

  return (
    <>
      <ListBox
        aria-label="Sandwich contents"
        selectionMode="multiple"
        selectedKeys={selected}
        onSelectionChange={setSelected}
      >
        <Item id="lettuce">
          Lettuce
        </Item>
        <Item id="tomato">
          Tomato
        </Item>
        <Item id="cheese">
          Cheese
        </Item>
        <Item id="tuna">
          Tuna Salad
        </Item>
        <Item id="egg">
          Egg Salad
        </Item>
        <Item id="ham">
          Ham
        </Item>
      </ListBox>
      <p>
        Current selection
        (controlled):
        {' '}
        {[...selected]
          .join(', ')}
      </p>
    </>
  );
}

Selection behavior#

By default, ListBox uses the "toggle" selection behavior, which behaves like a checkbox group: clicking, tapping, or pressing the Space or Enter keys toggles selection for the focused row. Using the arrow keys moves focus but does not change selection.

When selectionBehavior is set to "replace", clicking a row with the mouse replaces the selection with only that row. Using the arrow keys moves both focus and selection. To select multiple rows, modifier keys such as Ctrl, Cmd, and Shift can be used. On touch screen devices, selection always behaves as toggle since modifier keys may not be available.

These selection behaviors are defined in Aria Practices.

<ListBox
  aria-label="Sandwich contents"
  selectionMode="multiple"
  selectionBehavior="replace"
>
  <Item id="lettuce">Lettuce</Item>
  <Item id="tomato">Tomato</Item>
  <Item id="cheese">Cheese</Item>
  <Item id="tuna">Tuna Salad</Item>
  <Item id="egg">Egg Salad</Item>
  <Item id="ham">Ham</Item>
</ListBox>
<ListBox
  aria-label="Sandwich contents"
  selectionMode="multiple"
  selectionBehavior="replace"
>
  <Item id="lettuce">Lettuce</Item>
  <Item id="tomato">Tomato</Item>
  <Item id="cheese">Cheese</Item>
  <Item id="tuna">Tuna Salad</Item>
  <Item id="egg">Egg Salad</Item>
  <Item id="ham">Ham</Item>
</ListBox>
<ListBox
  aria-label="Sandwich contents"
  selectionMode="multiple"
  selectionBehavior="replace"
>
  <Item id="lettuce">
    Lettuce
  </Item>
  <Item id="tomato">
    Tomato
  </Item>
  <Item id="cheese">
    Cheese
  </Item>
  <Item id="tuna">
    Tuna Salad
  </Item>
  <Item id="egg">
    Egg Salad
  </Item>
  <Item id="ham">
    Ham
  </Item>
</ListBox>

Sections#

ListBox supports sections in order to group options. Sections can be used by wrapping groups of items in a Section element. Each Section takes a title prop.

Static items

import {Section} from 'react-aria-components';

<ListBox aria-label="Sandwich contents" selectionMode="multiple">
  <Section title="Veggies">
    <Item id="lettuce">Lettuce</Item>
    <Item id="tomato">Tomato</Item>
    <Item id="onion">Onion</Item>
  </Section>
  <Section title="Protein">
    <Item id="ham">Ham</Item>
    <Item id="tuna">Tuna</Item>
    <Item id="tofu">Tofu</Item>
  </Section>
  <Section title="Condiments">
    <Item id="mayo">Mayonaise</Item>
    <Item id="mustard">Mustard</Item>
    <Item id="ranch">Ranch</Item>
  </Section>
</ListBox>
import {Section} from 'react-aria-components';

<ListBox
  aria-label="Sandwich contents"
  selectionMode="multiple"
>
  <Section title="Veggies">
    <Item id="lettuce">Lettuce</Item>
    <Item id="tomato">Tomato</Item>
    <Item id="onion">Onion</Item>
  </Section>
  <Section title="Protein">
    <Item id="ham">Ham</Item>
    <Item id="tuna">Tuna</Item>
    <Item id="tofu">Tofu</Item>
  </Section>
  <Section title="Condiments">
    <Item id="mayo">Mayonaise</Item>
    <Item id="mustard">Mustard</Item>
    <Item id="ranch">Ranch</Item>
  </Section>
</ListBox>
import {Section} from 'react-aria-components';

<ListBox
  aria-label="Sandwich contents"
  selectionMode="multiple"
>
  <Section title="Veggies">
    <Item id="lettuce">
      Lettuce
    </Item>
    <Item id="tomato">
      Tomato
    </Item>
    <Item id="onion">
      Onion
    </Item>
  </Section>
  <Section title="Protein">
    <Item id="ham">
      Ham
    </Item>
    <Item id="tuna">
      Tuna
    </Item>
    <Item id="tofu">
      Tofu
    </Item>
  </Section>
  <Section title="Condiments">
    <Item id="mayo">
      Mayonaise
    </Item>
    <Item id="mustard">
      Mustard
    </Item>
    <Item id="ranch">
      Ranch
    </Item>
  </Section>
</ListBox>

Dynamic items

The above example shows sections with static items. Sections can also be populated from a heirarchical data structure. Similarly to the props on ListBox, <Section> takes an array of data using the items prop.

function Example() {
  let options = [
    {name: 'Australian', children: [
      {id: 2, name: 'Koala'},
      {id: 3, name: 'Kangaroo'},
      {id: 4, name: 'Platypus'}
    ]},
    {name: 'American', children: [
      {id: 6, name: 'Bald Eagle'},
      {id: 7, name: 'Bison'},
      {id: 8, name: 'Skunk'}
    ]}
  ];
  let [selected, setSelected] = React.useState(new Set());

  return (
    <ListBox
      aria-label="Pick an animal"
      items={options}
      selectedKeys={selected}
      selectionMode="single"
      onSelectionChange={setSelected}
      width="size-2400">
      {item => (
        <Section id={item.name} items={item.children} title={item.name}>
          {item => <Item>{item.name}</Item>}
        </Section>
      )}
    </ListBox>
  );
}
function Example() {
  let options = [
    {
      name: 'Australian',
      children: [
        { id: 2, name: 'Koala' },
        { id: 3, name: 'Kangaroo' },
        { id: 4, name: 'Platypus' }
      ]
    },
    {
      name: 'American',
      children: [
        { id: 6, name: 'Bald Eagle' },
        { id: 7, name: 'Bison' },
        { id: 8, name: 'Skunk' }
      ]
    }
  ];
  let [selected, setSelected] = React.useState(new Set());

  return (
    <ListBox
      aria-label="Pick an animal"
      items={options}
      selectedKeys={selected}
      selectionMode="single"
      onSelectionChange={setSelected}
      width="size-2400"
    >
      {(item) => (
        <Section
          id={item.name}
          items={item.children}
          title={item.name}
        >
          {(item) => <Item>{item.name}</Item>}
        </Section>
      )}
    </ListBox>
  );
}
function Example() {
  let options = [
    {
      name: 'Australian',
      children: [
        {
          id: 2,
          name: 'Koala'
        },
        {
          id: 3,
          name:
            'Kangaroo'
        },
        {
          id: 4,
          name:
            'Platypus'
        }
      ]
    },
    {
      name: 'American',
      children: [
        {
          id: 6,
          name:
            'Bald Eagle'
        },
        {
          id: 7,
          name: 'Bison'
        },
        {
          id: 8,
          name: 'Skunk'
        }
      ]
    }
  ];
  let [
    selected,
    setSelected
  ] = React.useState(
    new Set()
  );

  return (
    <ListBox
      aria-label="Pick an animal"
      items={options}
      selectedKeys={selected}
      selectionMode="single"
      onSelectionChange={setSelected}
      width="size-2400"
    >
      {(item) => (
        <Section
          id={item.name}
          items={item
            .children}
          title={item
            .name}
        >
          {(item) => (
            <Item>
              {item.name}
            </Item>
          )}
        </Section>
      )}
    </ListBox>
  );
}

Accessibility

Sections without a title must provide an aria-label for accessibility.

Complex options#

By default, options items that only contain text will be labeled by the contents of the item. The <Text> component may be used to add a description using the slot="description" prop. Slots improve screen reader announcements, and may also be used for styling and layout purposes.

NOTE: listbox options cannot contain interactive content (e.g. buttons, checkboxes, etc.). For these cases, see GridList instead.

import {Text} from 'react-aria-components';

<ListBox aria-label="Permissions" selectionMode="single">
  <Item textValue="Read">
    <Text slot="label">Read</Text>
    <Text slot="description">Read only</Text>
  </Item>
  <Item textValue="Write">
    <Text slot="label">Write</Text>
    <Text slot="description">Read and write only</Text>
  </Item>
  <Item textValue="Admin">
    <Text slot="label">Admin</Text>
    <Text slot="description">Full access</Text>
  </Item>
</ListBox>
import {Text} from 'react-aria-components';

<ListBox aria-label="Permissions" selectionMode="single">
  <Item textValue="Read">
    <Text slot="label">Read</Text>
    <Text slot="description">Read only</Text>
  </Item>
  <Item textValue="Write">
    <Text slot="label">Write</Text>
    <Text slot="description">Read and write only</Text>
  </Item>
  <Item textValue="Admin">
    <Text slot="label">Admin</Text>
    <Text slot="description">Full access</Text>
  </Item>
</ListBox>
import {Text} from 'react-aria-components';

<ListBox
  aria-label="Permissions"
  selectionMode="single"
>
  <Item textValue="Read">
    <Text slot="label">
      Read
    </Text>
    <Text slot="description">
      Read only
    </Text>
  </Item>
  <Item textValue="Write">
    <Text slot="label">
      Write
    </Text>
    <Text slot="description">
      Read and write
      only
    </Text>
  </Item>
  <Item textValue="Admin">
    <Text slot="label">
      Admin
    </Text>
    <Text slot="description">
      Full access
    </Text>
  </Item>
</ListBox>

Asynchronous loading#

This example uses the useAsyncList hook to handle asynchronous loading of data from a server. You may additionally want to display a spinner to indicate the loading state to the user, or support features like infinite scroll to load more data.

import {useAsyncList} from 'react-stately';

function AsyncLoadingExample() {
  let list = useAsyncList({
    async load({ signal, filterText }) {
      let res = await fetch(
        `https://pokeapi.co/api/v2/pokemon`,
        { signal }
      );
      let json = await res.json();

      return {
        items: json.results
      };
    }
  });

  return (
    <ListBox
      aria-label="Pick a Pokemon"
      items={list.items}
      selectionMode="single"
    >
      {(item) => <Item id={item.name}>{item.name}</Item>}
    </ListBox>
  );
}
import {useAsyncList} from 'react-stately';

function AsyncLoadingExample() {
  let list = useAsyncList({
    async load({ signal, filterText }) {
      let res = await fetch(
        `https://pokeapi.co/api/v2/pokemon`,
        { signal }
      );
      let json = await res.json();

      return {
        items: json.results
      };
    }
  });

  return (
    <ListBox
      aria-label="Pick a Pokemon"
      items={list.items}
      selectionMode="single"
    >
      {(item) => <Item id={item.name}>{item.name}</Item>}
    </ListBox>
  );
}
import {useAsyncList} from 'react-stately';

function AsyncLoadingExample() {
  let list =
    useAsyncList({
      async load(
        {
          signal,
          filterText
        }
      ) {
        let res =
          await fetch(
            `https://pokeapi.co/api/v2/pokemon`,
            { signal }
          );
        let json =
          await res
            .json();

        return {
          items:
            json.results
        };
      }
    });

  return (
    <ListBox
      aria-label="Pick a Pokemon"
      items={list.items}
      selectionMode="single"
    >
      {(item) => (
        <Item
          id={item.name}
        >
          {item.name}
        </Item>
      )}
    </ListBox>
  );
}

Disabled items#

ListBox supports marking items as disabled using the disabledKeys prop. Each key in this list corresponds with the id prop passed to the Item component, or automatically derived from the values passed to the items prop. See Collections for more details.

<ListBox
  aria-label="Choose sandwich contents"
  selectionMode="multiple"
  disabledKeys={['tuna']}
>
  <Item id="lettuce">Lettuce</Item>
  <Item id="tomato">Tomato</Item>
  <Item id="cheese">Cheese</Item>
  <Item id="tuna">Tuna Salad</Item>
  <Item id="egg">Egg Salad</Item>
  <Item id="ham">Ham</Item>
</ListBox>
<ListBox
  aria-label="Choose sandwich contents"
  selectionMode="multiple"
  disabledKeys={['tuna']}
>
  <Item id="lettuce">Lettuce</Item>
  <Item id="tomato">Tomato</Item>
  <Item id="cheese">Cheese</Item>
  <Item id="tuna">Tuna Salad</Item>
  <Item id="egg">Egg Salad</Item>
  <Item id="ham">Ham</Item>
</ListBox>
<ListBox
  aria-label="Choose sandwich contents"
  selectionMode="multiple"
  disabledKeys={[
    'tuna'
  ]}
>
  <Item id="lettuce">
    Lettuce
  </Item>
  <Item id="tomato">
    Tomato
  </Item>
  <Item id="cheese">
    Cheese
  </Item>
  <Item id="tuna">
    Tuna Salad
  </Item>
  <Item id="egg">
    Egg Salad
  </Item>
  <Item id="ham">
    Ham
  </Item>
</ListBox>

Advanced customization#


Composition#

If you need to customize one of the components within a ListBox, such as Item or Section, in many cases you can create a wrapper component. This lets you customize the props passed to the component.

function MyItem(props) {
  return <Item {...props} className="my-item" />
}
function MyItem(props) {
  return <Item {...props} className="my-item" />
}
function MyItem(props) {
  return (
    <Item
      {...props}
      className="my-item"
    />
  );
}

Hooks#

If you need to customize things even further, such as accessing internal state or customizing DOM structure, you can drop down to the lower level Hook-based API. See useListBox for more details.