Skip to main content
0.17.0
View Zag.js on Github
Join the Discord server

Combobox

A combobox is an input widget with an associated popup that enables users to select a value from a collection of possible values.

Properties

Features

  • Support for filtering a list of options by typing.
  • Support for disabled options
  • Support for custom user input values
  • Support for mouse, touch, and keyboard interactions
  • Keyboard support for opening the combo box list box using the arrow keys, including automatically focusing the first or last item accordingly

Installation

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

npm install @zag-js/combobox @zag-js/react # or yarn add @zag-js/combobox @zag-js/react

This command will install the framework agnostic combobox logic and the reactive utilities for your framework of choice.

Anatomy

To set up the combobox 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.

On a high level, the combobox consists of:

  • Root: The root container for the combobox
  • Label: The label that gives the user information on the combobox
  • Positioner: The element that positions the combobox dynamically.
  • Content: The element that contains the options.
  • Trigger: The element that toggles the combobox menu.
  • Control: The element that contains the input and trigger.
  • Input: The element that receives user input.
  • Item: The combobox item or option element.
  • ItemIndicator: The element that indicates the selected item.
  • ItemGroup: The element used to group related items.

Usage

First, import the combobox package into your project

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

The combobox package exports these functions:

  • machine — The state machine logic for the combobox widget.
  • connect — The function that translates the machine's state to JSX attributes and event handlers.
  • collection - The function that creates a collection interface from an array of items.

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

import * as combobox from "@zag-js/combobox" import { useMachine, normalizeProps } from "@zag-js/react" const comboboxData = [ { label: "Zambia", code: "ZA" }, { label: "Benin", code: "BN" }, //... ] export function Combobox() { const [options, setOptions] = useState(comboboxData) const collection = combobox.collection({ items: comboboxData, itemToValue: (item) => item.code, itemToString: (item) => item.label, }) const [state, send] = useMachine( combobox.machine({ id: useId(), collection, onOpen() { setOptions(comboboxData) }, onInputChange({ value }) { const filtered = comboboxData.filter((item) => item.label.toLowerCase().includes(value.toLowerCase()), ) setOptions(filtered.length > 0 ? filtered : comboboxData) }, }), { context: { collection }, }, ) const api = combobox.connect(state, send, normalizeProps) return ( <div> <div {...api.rootProps}> <label {...api.labelProps}>Select country</label> <div {...api.controlProps}> <input {...api.inputProps} /> <button {...api.triggerProps}></button> </div> </div> <div {...api.positionerProps}> {options.length > 0 && ( <ul {...api.contentProps}> {options.map((item) => ( <li key={item.code} {...api.getItemProps({ item })}> {item.label} </li> ))} </ul> )} </div> </div> ) }

Disabling the combobox

To make a combobox disabled, set the context's disabled property to true

const [state, send] = useMachine( combobox.machine({ disabled: true, }), )

Disabling an option

To make a combobox option disabled, pass the isItemDisabled property to the collection function.

const [state, send] = useMachine( combobox.machine({ id: useId(), collection: combobox.collection({ items: countries, isItemDisabled(item) { return item.disabled }, }), }), )

Close on select

This behaviour ensures that the menu is closed when an option is selected and is true by default. It's only concerned with when an option is selected with pointer or enter key. To disable the behaviour, set the closeOnSelect property in the machine's context to false.

const [state, send] = useMachine( combobox.machine({ closeOnSelect: false, }), )

Making the combobox readonly

To make a combobox readonly, set the context's readOnly property to true

const [state, send] = useMachine( combobox.machine({ readOnly: true, }), )

Listening for highlight changes

When an option is highlighted with the pointer or keyboard, the highlightedOption property in the machine's context is updated. You can listen for this change and do something with it.

const [state, send] = useMachine( combobox.machine({ id: useId(), onHighlight(details) { // details => { label: string, value: string, relatedTarget: HTMLElement | null } console.log(details) }, }), )

Listening for selection changes

When an option is selected, the selectedOption property in the machine's context is updated. You can listen for this change and do something with it.

const [state, send] = useMachine( combobox.machine({ onSelect(details) { // details => { label: string, value: string, relatedTarget: HTMLElement | null } console.log(details) }, }), )

Usage within forms

The combobox works when placed within a form and the form is submitted. We achieve this by:

  • ensuring we emit the input event as the value changes.
  • adding a name attribute to the input so the value can be accessed in the FormData.

To get this feature working you need to pass a name option to the context.

const [state, send] = useMachine( combobox.machine({ name: "countries", }), )

Styling guide

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

Open and closed state

When the combobox is open or closed, the data-state attribute is added to the content,control, input and control parts.

[data-part="control"][data-state="open|closed"] { /* styles for control open or state */ } [data-part="input"][data-state="open|closed"] { /* styles for control open or state */ } [data-part="trigger"][data-state="open|closed"] { /* styles for control open or state */ } [data-part="content"][data-state="open|closed"] { /* styles for control open or state */ }

Focused State

When the combobox is focused, the data-focus attribute is added to the control and label parts.

[data-part="control"][data-focus] { /* styles for control focus state */ } [data-part="label"][data-focus] { /* styles for label focus state */ }

Disabled State

When the combobox is disabled, the data-disabled attribute is added to the label, control, trigger and option parts.

[data-part="label"][data-disabled] { /* styles for label disabled state */ } [data-part="control"][data-disabled] { /* styles for control disabled state */ } [data-part="trigger"][data-disabled] { /* styles for trigger disabled state */ } [data-part="item"][data-disabled] { /* styles for item disabled state */ }

Invalid State

When the combobox is invalid, the data-invalid attribute is added to the root, label, control and input parts.

[data-part="root"][data-invalid] { /* styles for root invalid state */ } [data-part="label"][data-invalid] { /* styles for label invalid state */ } [data-part="control"][data-invalid] { /* styles for control invalid state */ } [data-part="input"][data-invalid] { /* styles for input invalid state */ }

Selected State

When a combobox item is selected, the data-selected attribute is added to the item part.

[data-part="item"][data-state="checked|unchecked"] { /* styles for item selected state */ }

Highlighted State

When a combobox item is highlighted, the data-highlighted attribute is added to the item part.

[data-part="item"][data-highlighted] { /* styles for item highlighted state */ }

Methods and Properties

The combobox's api exposes the following methods:

  • isFocusedbooleanWhether the combobox is focused
  • isOpenbooleanWhether the combobox content or listbox is open
  • isInputValueEmptybooleanWhether the combobox input is empty
  • inputValuestringThe current value of the combobox input
  • focusedOptionOptionDataThe currently focused option (by pointer or keyboard)
  • selectedValuestringThe currently selected option value
  • setValue(value: string | OptionData) => voidFunction to set the combobox value
  • setInputValue(value: string) => voidFunction to set the combobox input value
  • clearValue() => voidFunction to clear the combobox input value and selected value
  • focus() => voidFunction to focus the combobox input
  • getOptionState(props: OptionProps) => { isDisabled: boolean; isHighlighted: boolean; isChecked: boolean; }Returns the state of an option

Edit this page on GitHub

On this page