Skip to main content

A listbox component that displays a list of selectable options, supporting both single and multiple selection modes.

Loading...

Features

  • Supports single, multiple, or no selection
  • Can be controlled or uncontrolled
  • Fully managed keyboard navigation (arrow keys, home, end, etc.)
  • Vertical and horizontal orientation
  • Typeahead to allow focusing the matching item
  • Supports items, labels, groups of items
  • Supports grid and list layouts

Installation

To use the listbox machine in your project, run the following command in your command line:

npm install @zag-js/listbox @zag-js/svelte # or yarn add @zag-js/listbox @zag-js/svelte

Anatomy

To set up the listbox correctly, you'll need to understand its anatomy and how we name its parts.

Each part includes a data-part attribute to help identify them in the DOM.

No anatomy available for listbox

Usage

First, import the listbox package into your project

import * as listbox from "@zag-js/listbox"

The listbox package exports two key functions:

  • machine — The state machine logic for the listbox widget.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.

You'll also need to provide a unique id to the useMachine hook. This is used to ensure that every part has a unique identifier.

Next, import the required hooks and functions for your framework and use the listbox machine in your project 🔥

<script lang="ts"> import * as listbox from "@zag-js/listbox" import { normalizeProps, useMachine } from "@zag-js/svelte" import { createUniqueId } from "@zag-js/utils" const data = [ { label: "Nigeria", value: "NG" }, { label: "United States", value: "US" }, { label: "Canada", value: "CA" }, { label: "Japan", value: "JP" }, ] const collection = listbox.collection({ items: data }) const service = useMachine(listbox.machine, { id: createUniqueId(), collection, }) const api = $derived(listbox.connect(service, normalizeProps)) </script> <div {...api.getRootProps()}> <label {...api.getLabelProps()}>Select country</label> <ul {...api.getContentProps()}> {#each data as item} <li {...api.getItemProps({ item })}> <span {...api.getItemTextProps({ item })}>{item.label}</span> <span {...api.getItemIndicatorProps({ item })}></span> </li> {/each} </ul> </div>

Setting the initial selection

To set the initial selection, you can use the defaultValue property.

const service = useMachine(listbox.machine, { // ... defaultValue: ["item-1", "item-2"], })

Controlling the selection

To control the selection programmatically, you can use the value and onValueChange properties.

const service = useMachine(listbox.machine, { value: ["item-1", "item-2"], onValueChange: (value) => { console.log(value) }, })

Filtering

The listbox component supports filtering of items via api.getInputProps. Here's an example of how to support searching through a list of items.

<script lang="ts"> import * as listbox from "@zag-js/listbox" import { createFilter } from "@zag-js/i18n-utils" import { normalizeProps, useMachine } from "@zag-js/svelte" interface Item { label: string value: string } const data: Item[] = [ { label: "Nigeria", value: "NG" }, { label: "United States", value: "US" }, { label: "Canada", value: "CA" }, { label: "Japan", value: "JP" }, ] const filter = createFilter({ sensitivity: "base" }) let search = $state("") const collection = $derived.by(() => { const items = data.filter((item) => filter.startsWith(item.label, search)) return listbox.collection({ items }) }) const id = $props.id() const service = useMachine(listbox.machine as listbox.Machine<Item>, { id, get collection() { return collection }, typeahead: false, }) const api = $derived(listbox.connect(service, normalizeProps)) </script> <div {...api.getRootProps()}> <input {...api.getInputProps({ autoHighlight: true })} bind:value={search} /> <ul {...api.getContentProps()}> {#each collection.items as item (item.value)} <li {...api.getItemProps({ item })}> <span {...api.getItemTextProps({ item })}>{item.label}</span> <span {...api.getItemIndicatorProps({ item })}></span> </li> {/each} </ul> </div>

Selecting multiple items

To enable multiple selection, set the selectionMode property to multiple or extended.

const service = useMachine(listbox.machine, { // ... selectionMode: "multiple", })

Selection Modes

By default, a user can select a single item in a listbox. You can set the selectionMode property to a SelectionMode enumeration value to enable multi-selection. Here are the selection mode values.

  • single: A user can select a single item using the space bar, mouse click, or touch tap.
  • multiple: A user can select multiple items using the space bar, mouse click, or touch tap to toggle selection on the focused item. Using the arrow keys, a user can move focus independently of selection.
  • extended: With no modifier keys like Ctrl, Cmd or Shift: the behavior is the same as single selection.
const service = useMachine(listbox.machine, { // ... selectionMode: "extended", })

Disabling items

To disable an item, you can use the disabled property.

api.getItemProps({ // ... disabled: true, })

To disable the entire listbox, you can use the disabled property.

const service = useMachine(listbox.machine, { disabled: true, })

Grid layout

To enable a grid layout, provide a grid collection to the collection property.

const service = useMachine(listbox.machine, { collection: listbox.gridCollection({ items: [], columnCount: 3, }), })
<script lang="ts"> import * as listbox from "@zag-js/listbox" import { normalizeProps, useMachine } from "@zag-js/svelte" import { createUniqueId } from "@zag-js/utils" const data = [ { label: "Red", value: "red" }, { label: "Green", value: "green" }, { label: "Blue", value: "blue" }, { label: "Yellow", value: "yellow" }, { label: "Purple", value: "purple" }, { label: "Orange", value: "orange" }, ] const collection = listbox.gridCollection({ items: data, columnCount: 3, }) const service = useMachine(listbox.machine, { id: createUniqueId(), collection, }) const api = $derived(listbox.connect(service, normalizeProps)) </script> <div {...api.getRootProps()}> <label {...api.getLabelProps()}>Select color</label> <div {...api.getContentProps()} style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px" > {#each data as item} <div {...api.getItemProps({ item })}> <span {...api.getItemTextProps({ item })}>{item.label}</span> <span {...api.getItemIndicatorProps({ item })}></span> </div> {/each} </div> </div>

Styling guide

Earlier, we mentioned that each listbox part has a data-part attribute added to them to select and style them in the DOM.

[data-scope="listbox"][data-part="root"] { /* styles for the root part */ } [data-scope="listbox"][data-part="label"] { /* styles for the label part */ } [data-scope="listbox"][data-part="content"] { /* styles for the content part */ } [data-scope="listbox"][data-part="item"] { /* styles for the item part */ } [data-scope="listbox"][data-part="itemGroup"] { /* styles for the item group part */ }

Focused state

The focused state is applied to the item that is currently focused.

[data-scope="listbox"][data-part="item"][data-focused] { /* styles for the focused item part */ }

Selected state

The selected state is applied to the item that is currently selected.

[data-scope="listbox"][data-part="item"][data-selected] { /* styles for the selected item part */ }

Disabled state

The disabled state is applied to the item that is currently disabled.

[data-scope="listbox"][data-part="item"][data-disabled] { /* styles for the disabled item part */ }

Methods and Properties

Machine Context

The listbox machine exposes the following context properties:

  • orientation"horizontal" | "vertical"The orientation of the listbox.
  • collectionGridCollection<T>The item collection
  • idsPartial<{ root: string; content: string; label: string; item: (id: string | number) => string; itemGroup: (id: string | number) => string; itemGroupLabel: (id: string | number) => string; }>The ids of the elements in the listbox. Useful for composition.
  • disabledbooleanWhether the listbox is disabled
  • disallowSelectAllbooleanWhether to disallow selecting all items when `meta+a` is pressed
  • onHighlightChange(details: HighlightChangeDetails<T>) => voidThe callback fired when the highlighted item changes.
  • onValueChange(details: ValueChangeDetails<T>) => voidThe callback fired when the selected item changes.
  • valuestring[]The controlled keys of the selected items
  • defaultValuestring[]The initial default value of the listbox when rendered. Use when you don't need to control the value of the listbox.
  • highlightedValuestringThe controlled key of the highlighted item
  • defaultHighlightedValuestringThe initial value of the highlighted item when opened. Use when you don't need to control the highlighted value of the listbox.
  • loopFocusbooleanWhether to loop the keyboard navigation through the options
  • selectionModeSelectionModeHow multiple selection should behave in the listbox. - `single`: The user can select a single item. - `multiple`: The user can select multiple items without using modifier keys. - `extended`: The user can select multiple items by using modifier keys.
  • scrollToIndexFn(details: ScrollToIndexDetails) => voidFunction to scroll to a specific index
  • selectOnHighlightbooleanWhether to select the item when it is highlighted
  • deselectablebooleanWhether to disallow empty selection
  • typeaheadbooleanWhether to enable typeahead on the listbox
  • onSelect(details: SelectionDetails) => voidFunction called when an item is selected
  • dir"ltr" | "rtl"The document's text/writing direction.
  • idstringThe unique identifier of the machine.
  • getRootNode() => ShadowRoot | Node | DocumentA root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The listbox api exposes the following methods:

  • emptybooleanWhether the select value is empty
  • highlightedValuestringThe value of the highlighted item
  • highlightedItemVThe highlighted item
  • highlightValue(value: string) => voidFunction to highlight a value
  • clearHighlightedValueVoidFunctionFunction to clear the highlighted value
  • selectedItemsV[]The selected items
  • hasSelectedItemsbooleanWhether there's a selected option
  • valuestring[]The selected item keys
  • valueAsStringstringThe string representation of the selected items
  • selectValue(value: string) => voidFunction to select a value
  • selectAllVoidFunctionFunction to select all values. **Note**: This should only be called when the selectionMode is `multiple` or `extended`. Otherwise, an exception will be thrown.
  • setValue(value: string[]) => voidFunction to set the value of the select
  • clearValue(value?: string) => voidFunction to clear the value of the select. If a value is provided, it will only clear that value, otherwise, it will clear all values.
  • getItemState(props: ItemProps<any>) => ItemStateReturns the state of a select item
  • collectionListCollection<V>Function to toggle the select
  • disabledbooleanWhether the select is disabled

Data Attributes

Root
data-scope
listbox
data-part
root
data-orientation
The orientation of the listbox
data-disabled
Present when disabled
Input
data-scope
listbox
data-part
input
data-disabled
Present when disabled
Label
data-scope
listbox
data-part
label
data-disabled
Present when disabled
ValueText
data-scope
listbox
data-part
value-text
data-disabled
Present when disabled
Item
data-scope
listbox
data-part
item
data-value
The value of the item
data-selected
Present when selected
data-state
"checked" | "unchecked"
data-orientation
The orientation of the item
data-highlighted
Present when highlighted
data-disabled
Present when disabled
ItemText
data-scope
listbox
data-part
item-text
data-state
"checked" | "unchecked"
data-disabled
Present when disabled
data-highlighted
Present when highlighted
ItemIndicator
data-scope
listbox
data-part
item-indicator
data-state
"checked" | "unchecked"
ItemGroup
data-scope
listbox
data-part
item-group
data-disabled
Present when disabled
data-orientation
The orientation of the item
data-empty
Present when the content is empty
Content
data-scope
listbox
data-part
content
data-activedescendant
The id the active descendant of the content
data-orientation
The orientation of the content
data-empty
Present when the content is empty

CSS Variables

Content
--column-count
The column count value for the Content

Accessibility

Adheres to the Listbox WAI-ARIA design pattern.

Edit this page on GitHub