> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Listbox - Web

> Listboxes allow a user to select one or more items from a list of options.

> [!NOTE]
>
> For development, import `Listbox` from
> `@servicetitan/anvil2`. For design, find it in the
> [Anvil2 file](https://www.figma.com/design/xaNQsvRKczoxu7pj0M6QT8/Anvil2-v3?node-id=194-537).

![](https://mintcdn.com/servicetitan/uz2PQSvO75TRhQ38/images/docs/web/components/shared/listbox-overview.png?fit=max\&auto=format\&n=uz2PQSvO75TRhQ38\&q=85\&s=e6e8cd9080f3c666cdbbc33c3a374818)

## Anatomy

The Listbox consists of four primary elements that work together to allow users to select one or more items from a list of options.

![Listbox
anatomy](https://mintcdn.com/servicetitan/aip5_7K1pHSn1Axn/images/docs/web/components/listbox/design/listbox-anatomy.png?fit=max\&auto=format\&n=aip5_7K1pHSn1Axn\&q=85\&s=59e3173b834c66af93649d145b1dde61)

1. Option
2. Group divider
3. Group header
4. Selected option

## Options

The Listbox supports grouping and multiple selection configurations to accommodate various selection scenarios.

### Grouping

listbox-optiongroup

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/listbox-optiongroup?file=src/App.tsx)

```tsx lines theme={null}
import { Listbox } from "@servicetitan/anvil2";

type Item = {
  id: number;
  label: string;
  disabled?: boolean;
};

function App() {
  const itemGroups: Item[][] = [
    [
      { id: 1, label: "Apple" },
      { id: 2, label: "Banana" },
    ],
    [
      { id: 1, label: "Yam" },
      { id: 2, label: "Zucchini" },
    ],
  ];

  return (
    <Listbox
      aria-label="Single select example"
      defaultSelected={itemGroups[0][1].label}
    >
      <Listbox.OptionGroup label="Fruits">
        {itemGroups[0].map((item, i) => {
          return (
            <Listbox.Option
              key={i}
              label={`${item.label}`}
              disabled={item.disabled}
            >
              {item.label}
            </Listbox.Option>
          );
        })}
      </Listbox.OptionGroup>
      <Listbox.OptionGroup label="Vegetables">
        {itemGroups[1].map((item, i) => {
          return (
            <Listbox.Option key={i} label={`${item.label}`}>
              {item.label}
            </Listbox.Option>
          );
        })}
      </Listbox.OptionGroup>
    </Listbox>
  );
}

export default App;
```

### Multiple selection

listbox-selectionmode-multiple

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/listbox-selectionmode-multiple?file=src/App.tsx)

```tsx lines theme={null}
import { Listbox } from "@servicetitan/anvil2";

type Item = {
  id: number;
  label: string;
  disabled?: boolean;
};

function App() {
  const itemGroups: Item[][] = [
    [
      { id: 1, label: "Apple" },
      { id: 2, label: "Banana" },
    ],
    [
      { id: 1, label: "Yam" },
      { id: 2, label: "Zucchini" },
    ],
  ];

  return (
    <Listbox
      selectionMode="multiple"
      aria-label="Single select example"
      defaultSelected={[
        itemGroups[0][1].label,
        itemGroups[1][0].label,
        itemGroups[1][1].label,
      ]}
    >
      <Listbox.OptionGroup label="Fruits">
        {itemGroups[0].map((item, i) => {
          return (
            <Listbox.Option
              key={i}
              label={`${item.label}`}
              disabled={item.disabled}
            >
              {item.label}
            </Listbox.Option>
          );
        })}
      </Listbox.OptionGroup>
      <Listbox.OptionGroup label="Vegetables">
        {itemGroups[1].map((item, i) => {
          return (
            <Listbox.Option key={i} label={`${item.label}`}>
              {item.label}
            </Listbox.Option>
          );
        })}
      </Listbox.OptionGroup>
    </Listbox>
  );
}

export default App;
```

## Behavior

The Listbox responds to user interaction with typeahead focus functionality and overflow handling.

#### Typeahead focus

When the Listbox is in focus, a user can type a character to move focus to the next item that starts with that character. If typed quickly (750ms), focus will move to the next item that matches the string of characters typed.

#### Overflow handling

listbox-overflow

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/listbox-overflow?file=src/App.tsx)

```tsx lines theme={null}
import { Listbox } from "@servicetitan/anvil2";

type Item = {
  id: number;
  label: string;
  disabled?: boolean;
  "data-interactive"?: string;
};

function App() {
  const itemGroups: Item[][] = [
    [
      { id: 1, label: "This is a really long option that will wrap" },
      { id: 2, label: "This is a really long selected option that will wrap" },
    ],
    [{ id: 1, label: "Option" }],
  ];

  return (
    <Listbox
      aria-label="Single select example"
      defaultSelected={itemGroups[0][1].label}
      style={{ maxWidth: "280px" }}
    >
      <Listbox.OptionGroup label="Group">
        {itemGroups[0].map((item, i) => {
          return (
            <Listbox.Option
              key={i}
              label={`${item.label}`}
              disabled={item.disabled}
            >
              {item.label}
            </Listbox.Option>
          );
        })}
      </Listbox.OptionGroup>
      <Listbox.OptionGroup label="This is a really long group that will wrap">
        {itemGroups[1].map((item, i) => {
          return (
            <Listbox.Option key={i} label={`${item.label}`}>
              {item.label}
            </Listbox.Option>
          );
        })}
      </Listbox.OptionGroup>
    </Listbox>
  );
}

export default App;
```

#### Visual States

listbox-data-interactive

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/listbox-data-interactive?file=src/App.tsx)

```tsx lines theme={null}
import { Listbox, Flex } from "@servicetitan/anvil2";

type Item = {
  id: number;
  label: string;
  disabled?: boolean;
  "data-interactive"?: string;
};

function App() {
  const itemsList: Item[] = [
    { id: 1, label: "Rest" },
    { id: 2, label: "Hover", "data-interactive": "hover" },
    { id: 3, label: "Focus", "data-interactive": "focus-visible" },
    { id: 4, label: "Disabled", disabled: true },
  ];

  return (
    <Flex gap={4}>
      <Listbox items={itemsList} gap={4} flex="1">
        {({ items }) =>
          items.map((item) => (
            <Listbox.Option
              key={item.label}
              item={item}
              disabled={item.disabled}
              {...(item["data-interactive"]
                ? { "data-interactive": item["data-interactive"] }
                : {})}
            >
              {item.label}
            </Listbox.Option>
          ))
        }
      </Listbox>
      <Listbox<Item>
        selectionMode="multiple"
        items={itemsList}
        gap={4}
        defaultSelected={itemsList}
        flex="1"
      >
        {({ items }) =>
          items.map((item) => (
            <Listbox.Option
              key={item.label}
              item={item}
              disabled={item.disabled}
              {...(item["data-interactive"]
                ? { "data-interactive": item["data-interactive"] }
                : {})}
            >
              Selected {item.label}
            </Listbox.Option>
          ))
        }
      </Listbox>
    </Flex>
  );
}

export default App;
```

## Usage Guidelines

| Standard | Requirement |
| - | - |
| DoProvide selectable items in an overlay | Listboxes are used as a set of selectable items in an overlay. It is most commonly used for the options in a Combobox or when building a custom menu. |

### Alternatives

#### Listbox vs List View

Use Listbox when using simple selections that do not require any customization. Use List View when requiring customized styling or secondary actions. List View may also be used when emphasizing to users that multi-selection is possible.

#### Listbox vs Radio

For simple selection lists in a form, use a Radio.

For selection lists that need to be an overlay, use a Listbox with a Combobox.

## Content

Content within the Listbox should clearly communicate each option through descriptive labels.

## Keyboard Interaction

Users can navigate the Listbox using standard keyboard controls.

| Key | Description |
| - | - |
| Tab | Moves to the next tab-able element outside of the Listbox. |
| Arrow keys | Navigates through the available list items |
| Space | Activates or deactivates the focused Listbox item. |
| Home | Moves focus to the first item in the Listbox. |
| End | Moves focus to the last item in the Listbox. |
| Printable character | Moves focus to the next item that has the character typed. If multiple characters are typed quickly (\<750ms), focus moves to the next item with that combination of characters. If the next character takes longer than 750ms, focus is moved to the next item that starts with the new character typed. |

### Accessibility

Listbox provides all of the necessary accessibility functionality.

#### Don't add additional interactive elements to the Listbox

The Listbox is designed for simple interactions, and cannot handle additional interactions, such as secondary actions. If designing for this, consider adopting similar accessibility guidance found in [W3C ARIA's Data Grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/).

For more guidance on building accessible interactive lists, see [custom components best practices](https://anvil.servicetitan.com/docs/resources/contributing/accessible-custom-components.md).

## Code

**Implementation**

listbox-playground

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/listbox-playground?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Listbox } from "@servicetitan/anvil2";

function App() {
  const itemsList = [
    { id: 1, label: "First item" },
    { id: 2, label: "Second item" },
    { id: 3, label: "Third item", disabled: true },
    { id: 4, label: "Fourth item" },
    { id: 5, label: "Fifth item" },
  ];

  return (
    <Listbox items={itemsList} defaultSelected={itemsList[1]}>
      {({ items }) =>
        items.map((item) => (
          <Listbox.Option key={item.label} item={item} disabled={item.disabled}>
            {item.label}
          </Listbox.Option>
        ))
      }
    </Listbox>
  );
}

export default App;
```

## Common Examples

```tsx theme={null}
import { Listbox } from "@servicetitan/anvil2";

const ExampleComponent = () => {
  const itemsArray = [
    { id: 0, label: "First item" },
    // ... more items
  ];

  return (
    <Listbox items={itemsArray}>
      {({ items }) =>
        items.map((item) => (
          <Listbox.Option key={item.label} item={item}>
            {item.label}
          </Listbox.Option>
        ))
      }
    </Listbox>
  );
};
```

### Listbox Components

A listbox can be created using the `Listbox` component and its two sub-components:

* `Listbox.Option`: used to render the individual list options.
* `Listbox.OptionGroup`: optionally used to group listbox options.

**Single-select Listbox Props**

## Single-select `Listbox` using labels only

```tsx theme={null}
<Listbox
  selectionMode="single"
  defaultSelected="option-1"
  selected="option-1"
  disableAutoSelectOnFocus={false}
  onSelectionChange={(selected) => console.log(selected)}
>
  <Listbox.Option label="Option 1" value="option-1" />
  <Listbox.Option label="Option 2" value="option-2" />
</Listbox>
```

The `Listbox` component can accept any valid HTML `ul` props, with the exception that `children` could change depending on whether `items` is supplied.

**children** `ReactNode`

**defaultSelected** `string`

**disableAutoSelectOnFocus** `boolean`

**onSelectionChange** `(selected: string | undefined) =\> void`

**selected** `string`

**selectionMode** `"multiple" | "single"` (default: single)

See other tables for changes in types for `"multiple"`

## Single-select `Listbox` using `items` array

```tsx theme={null}
<Listbox
  items={itemsArray}
  selectionMode="single"
  defaultSelected={itemsArray[0]}
  selected={itemsArray[0]}
  disableAutoSelectOnFocus={false}
  onSelectionChange={(selected) => console.log(selected)}
>
  {({ items }) =>
    items.map((item) => (
      <Listbox.Option key={item.id} item={item}>
        {item.label}
      </Listbox.Option>
    ))
  }
</Listbox>
```

> [!NOTE]
>
> `T` represents any arbitrary type used for the `items`, and `ListboxItemType<T>` implies that it will include a `label`: `string` property.

**children** `(\{ items \}: \{ items: ListboxItemType\<T\>[] \}) =\> ReactNode`

**defaultSelected** `ListboxItemType\<T\>`

**disableAutoSelectOnFocus** `boolean`

**items** `ListboxItemType\<T\>[]`

**onSelectionChange** `(selected: ListboxItemType\<T\> | undefined) =\> void`

**selected** `ListboxItemType\<T\>`

**selectionMode** `"multiple" | "single"` (default: single)

See other tables for changes in types for `"multiple"`

**Multi-select Listbox Props**

## Multi-select `Listbox` using labels only

```tsx theme={null}
<Listbox
  selectionMode="multiple"
  defaultSelected={["option-1", "option-2"]}
  selected={["option-1", "option-2"]}
  onSelectionChange={(selected) => console.log(selected)}
>
  <Listbox.Option label="Option 1" value="option-1" />
  <Listbox.Option label="Option 2" value="option-2" />
</Listbox>
```

**children** `ReactNode`

**defaultSelected** `string[]`

**onSelectionChange** `(selected: string[] | undefined) =\> void`

**selected** `string[]`

**selectionMode** `"multiple" | "single"` (default: multiple)

See other tables for changes in types for `"single"`

## Multi-select `Listbox` using `items` array

```tsx theme={null}
<Listbox
  items={itemsArray}
  selectionMode="multiple"
  defaultSelected={[itemsArray[0], itemsArray[1]]}
  selected={[itemsArray[0], itemsArray[1]]}
  onSelectionChange={(selected) => console.log(selected)}
>
  {({ items }) =>
    items.map((item) => (
      <Listbox.Option key={item.id} item={item}>
        {item.label}
      </Listbox.Option>
    ))
  }
</Listbox>
```

> [!NOTE]
>
> `T` represents any arbitrary type used for the `items`, and `ListboxItemType<T>` implies that it will include a `label`: `string` property.

**children** `(\{ items \}: \{ items: ListboxItemType\<T\>[] \}) =\> ReactNode`

**defaultSelected** `ListboxItemType\<T\>[]`

**items** `ListboxItemType\<T\>[]`

**onSelectionChange** `(selected: ListboxItemType\<T\>[] | undefined) =\> void`

**selected** `ListboxItemType\<T\>[]`

**selectionMode** `"multiple" | "single"` (default: multiple)

See other tables for changes in types for `"single"`

**Listbox.Option Props**

```tsx theme={null}
<Listbox.Option item={item} label="Option 1" value="option-1" disabled={false}>
  Option content
</Listbox.Option>
```

## `Listbox.Option` Props

In addition to the props listed below, the `Listbox.Option` component can accept any valid HTML `li` props.

**disabled** `boolean`

**item** `\{ label: string; [key: string]: unknown; \}`

Use with `items` prop on parent `Listbox`

**label** `string`

Required if no `item` provided

**value** `string`


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.