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.
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.
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.
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.
<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.
| Prop | Type | Default |
|---|---|---|
isLoadingWhether to indicating that options are loading inside the listbox | boolean | |
isMultiSelectableNo description available | boolean | false |
isSearchableWhether to include the search input at the top of the list | boolean | true |
label (required)The label for the listbox, used for aria labelling | string | |
loadingFailedWhether to display a loading error message inside the listbox | boolean | |
maxHeightSet max height of scrollable container | enumnumber | "auto" | undefined | |
name (required)The name of the listbox, used for aria controls | string | |
onChange (required)Callback function called when selected value is changed | function(value: string) => void | |
onScrollToBottomCallback function called when the user scrolls to the bottom of the list | enum() => void | |
onSearchQueryChangeCallback function called when the users changes the search query | enum(newValue: string) => void | |
optionsThe options to render in the listbox | enumSingleListBoxOption[] | SingleListBoxGroup[] | undefined | [] |
selectedValue (required)The current selected value | enumstring | null |