# Listbox

A listbox provides a way to select options from a predefined, searchable list that is always visible.

Tabs: [Guidelines](/components/web/listbox/listbox.guidelines.md) · [Code](/components/web/listbox/listbox.code.md)

## Basic usage

`ListBox` renders only the list: an optional search field and the options. It has no trigger and no label of its own, so place it inside a container such as the `FilterMenu`.

```tsx
import {ListBox} from '@pleo-io/telescope'
```

`name` is used to build the ids that connect the search field to the list, so it must be unique on the page. `label` is announced by screen readers as the name of the search field.

```tsx
const CategoryList = () => {
    const [category, setCategory] = useState<string | null>(null)

    return (
        <ListBox
            name="category"
            label="Search categories"
            selectedValue={category}
            onChange={setCategory}
            options={[
                {value: 'travel', label: 'Travel'},
                {value: 'equipment', label: 'Equipment', description: 'Laptops, phones, desks'},
                {value: 'software', label: 'Software'},
            ]}
        />
    )
}
```

Users pick an option by click, `Enter` or `Space`, and `onChange` is called with its value. Use `↑` and `↓` to move between options, and `↓` in the search field to jump to the first one.

## Multiple selection

Set `isMultiSelectable` and mark the selected options with `isSelected`. `onChange` is still called with the value of the option that was clicked, so toggle it in your own state.

```tsx
const [selected, setSelected] = useState<string[]>([])

<ListBox
    isMultiSelectable
    name="team"
    label="Search teams"
    selectedValue={null}
    onChange={(value) =>
        setSelected((current) =>
            current.includes(value) ? current.filter((v) => v !== value) : [...current, value],
        )
    }
    options={teams.map((team) => ({
        value: team.id,
        label: team.name,
        isSelected: selected.includes(team.id),
    }))}
/>
```

## Groups

Pass groups instead of options to render labelled sections. Don't mix the two. `withSeparator` replaces the visible group title with a divider. The title stays available to screen readers.

```tsx
<ListBox
    name="people"
    label="Search people"
    selectedValue={null}
    onChange={handleChange}
    options={[
        {label: 'Teams', options: teamOptions},
        {label: 'Employees', withSeparator: true, options: employeeOptions},
    ]}
/>
```

## Search

The search field filters the options on `label`, ranking the best matches first. Set `isSearchable={false}` for short lists.

To search on the server or to track the query, use `onSearchQueryChange` and pass the new `options` back in. Combine it with `isLoading` and `loadingFailed` to show a loader or a "Could not load options" message. For long lists, `onScrollToBottom` fires once each time the user reaches the end of the list, so you can fetch the next page.

`maxHeight` limits the height of the scrollable list. It defaults to a height that fits the list with or without the search field.

## Listbox or Select

Both let people pick from a list of options, but they solve different problems.

| Use `ListBox` when…                                          | Use `Select` when…                                           |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| the list is the content of a surface, e.g. a `FilterMenu`    | the choice is a field in a form                              |
| the options should be visible straight away, with no trigger | the options should open from a field and close after picking |
| you need to control search, loading and paging from outside  | a standard dropdown is enough                                |
| you want to render the list in your own container            | you need `FormControl`, Formik or validation support         |

`Select` is built on React Select and brings its own field, label and menu. `ListBox` is a lightweight building block and doesn't come with a field, a label, validation or a form value. If you find yourself wrapping `ListBox` in a popover to build a form field, use `Select` instead.

## Localisation

The search placeholder, the clear button label and the empty, no-results and error messages come from the `Listbox` entry of the `TelescopeProvider` translations: `SearchPlaceholder`, `ClearSearch`, `NoMatchingResults`, `NoneFound` and `LoadError`.

## API reference

ListBox component is used to render a list of selectable options, for example to be used inside a filter dropdown. The list is searchable.

#### `isLoading`

Type: `boolean`

Default: -

Whether to indicating that options are loading inside the listbox

#### `isMultiSelectable`

Type: `boolean`

Default: `false`

No description available

#### `isSearchable`

Type: `boolean`

Default: `true`

Whether to include the search input at the top of the list

#### `label` (required)

Type: `string`

Default: -

The label for the listbox, used for aria labelling

#### `loadingFailed`

Type: `boolean`

Default: -

Whether to display a loading error message inside the listbox

#### `maxHeight`

Type: `number | "auto" | undefined`

Default: -

Set max height of scrollable container

#### `name` (required)

Type: `string`

Default: -

The name of the listbox, used for aria controls

#### `onChange` (required)

Type: `(value: string) => void`

Default: -

Callback function called when selected value is changed

#### `onScrollToBottom`

Type: `(() => void) | undefined`

##### Allowed values

- `() => void`

Default: -

Callback function called when the user scrolls to the bottom of the list

#### `onSearchQueryChange`

Type: `((newValue: string) => void) | undefined`

##### Allowed values

- `(newValue: string) => void`

Default: -

Callback function called when the users changes the search query

#### `options`

Type: `SingleListBoxOption[] | SingleListBoxGroup[] | undefined`

Default: `[]`

The options to render in the listbox

#### `selectedValue` (required)

Type: `string | null`

Default: -

The current selected value
