Skip to content
Destyler UI Destyler UI Destyler UI

Combobox

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

Features

Install

Install the component from your command line.

Terminal window
      
        
npm install @destyler/{combobox,vue}
Terminal window
      
        
npm install @destyler/{combobox,react}
Terminal window
      
        
npm install @destyler/{combobox,svelte}
Terminal window
      
        
npm install @destyler/{combobox,solid}

Anatomy

Import all parts and piece them together.

<script setup lang="ts">
import * as combobox from '@destyler/combobox'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, ref, useId } from 'vue'
const [state, send] = useMachine(
combobox.machine({
id: useId(),
}),
)
const api = computed(() => combobox.connect(state.value, send, normalizeProps))
</script>
<template>
<div v-bind="api.getRootProps()">
<div v-bind="api.getControlProps()">
<input v-bind="api.getInputProps()">
<button v-bind="api.getTriggerProps()" />
</div>
<div v-bind="api.getPositionerProps()">
<ul v-bind="api.getContentProps()">
<li v-bind="api.getItemProps({ item })" >
</li>
</ul>
</div>
</div>
</template>
import * as combobox from '@destyler/combobox'
import { normalizeProps, useMachine } from '@destyler/react'
import * as React from 'react'
export default function Combobox() {
const id = React.useId()
const [state, send] = useMachine(
combobox.machine({
id,
}),
)
const api = combobox.connect(state, send, normalizeProps)
return (
<div {...api.getRootProps()}>
<div {...api.getControlProps()}>
<input {...api.getInputProps()}/>
<button {...api.getTriggerProps()}></button>
</div>
<div {...api.getPositionerProps()} >
<ul {...api.getContentProps()} >
<li {...api.getItemProps({ item })}></li>
</ul>
</div>
</div>
)
}
<script lang="ts">
import * as combobox from '@destyler/combobox'
import { normalizeProps, useMachine } from '@destyler/svelte'
const [state, send] = useMachine(
combobox.machine({
id: crypto.randomUUID(),
}),
)
const api = $derived(combobox.connect(state, send, normalizeProps))
</script>
<div {...api.getRootProps()} >
<div {...api.getControlProps()} >
<input {...api.getInputProps()}>
<button {...api.getTriggerProps()}></button>
</div>
<div {...api.getPositionerProps()}>
<ul {...api.getContentProps()} >
<li {...api.getItemProps({ item })}></li>
</ul>
</div>
</div>
import * as combobox from '@destyler/combobox'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId } from 'solid-js'
export default function Combobox() {
const id = createUniqueId()
const [state, send] = useMachine(
combobox.machine({
id,
}),
)
const api = createMemo(() => combobox.connect(state, send, normalizeProps))
return (
<div {...api().getRootProps()}>
<div {...api().getControlProps()}>
<input {...api().getInputProps()} />
<button {...api().getTriggerProps()} ></button>
</div>
<div {...api().getPositionerProps()}>
<ul {...api().getContentProps()} >
<li {...api().getItemProps({ item })}></li>
</ul>
</div>
</div>
)
}

Setting the initial value

To set the initial value of the combobox, pass the value property to the Destyler’s context.

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

Selecting multiple values

To allow selecting multiple values, set the multiple property in the destyler’s context to true.

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

Use custom object format

By default, the combobox collection expects an array of items with label and value properties. To use a custom object format, pass the itemToString and itemToValue properties to the collection function.

  • itemToString — A function that returns the string representation of an item. Used to compare items when filtering.

  • itemToValue — A function that returns the unique value of an item.

  • itemToDisabled — A function that returns the disabled state of an item.

const collection = combobox.collection({
// custom object format
items: [
{ id: 1, fruit: "Vue", available: true, quantity: 10 },
{ id: 2, fruit: "React", available: false, quantity: 5 },
{ id: 3, fruit: "Svelte", available: true, quantity: 3 },
{ id: 4, fruit: "Solid", available: false, quantity: 0 },
//...
],
// convert item to string
itemToString(item) {
return item.fruit
},
// convert item to value
itemToValue(item) {
return item.id
},
// convert item to disabled state
itemToDisabled(item) {
return !item.available || item.quantity === 0
},
})
// use the collection
const [state, send] = useMachine(
combobox.machine({
id: useId(),
collection,
}),
)

Rendering the selected values outside the combobox

By default, the selected values of a combobox are displayed in the input element, when selecting multiple items, it is a better UX to render the selected value outside the combobox.

To achieve this you need to:

  • Set the selectionBehavior to clear, which clears the input value when an item is selected.

  • Set the multiple property to true to allow selecting multiple values.

  • Render the selected values outside the combobox.

const [state, send] = useMachine(
combobox.machine({
selectionBehavior: "clear",
multiple: true,
}),
)

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({
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 destyler’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, use the onHighlightChange property to listen for this change and do something with it.

const [state, send] = useMachine(
combobox.machine({
onHighlightChange(details) {
// details => { value: string | null; item: CollectionItem | null }
console.log(details)
},
}),
)

Listening for value changes

When an item is selected, use onValueChange property to listen for this change and do something with it.

const [state, send] = useMachine(
combobox.machine({
onValueChange(details) {
// details => { value: string[]; items: CollectionItem[] }
console.log(details)
},
}),
)

Use 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: "combobox",
}),
)

Allowing custom values

By default, the combobox only allows selecting values from the collection. To allow custom values, set the allowCustomValue property in the machine’s context to true.

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

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"] {
/* styles for control open or state */
}
[data-part="input"][data-state="open"] {
/* styles for control open or state */
}
[data-part="trigger"][data-state="open"] {
/* styles for control open or state */
}
[data-part="content"][data-state="open"] {
/* 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

Machine Context

The combobox machine exposes the following context properties:

open
boolean
Whether the combobox is open
open.controlled
boolean
Whether the combobox open state is controlled by the user
ids
Partial<{ root: string; label: string; control: string; input: string; content: string; trigger: string; clearTrigger: string; item(id: string, index?: number): string; positioner: string; itemGroup(id: string | number): string; itemGroupLabel(id: string | number): string; }>
The ids of the elements in the combobox. Useful for composition.
inputValue
string
The current value of the combobox's input
name
string
The `name` attribute of the combobox's input. Useful for form submission
form
string
The associate form of the combobox.
disabled
boolean
Whether the combobox is disabled
readOnly
boolean
Whether the combobox is readonly. This puts the combobox in a "non-editable" mode but the user can still interact with it
invalid
boolean
Whether the combobox is invalid
required
boolean
Whether the combobox is required
placeholder
string
The placeholder text of the combobox's input
highlightedValue
string
The active item's id. Used to set the `aria-activedescendant` attribute
value
string[]
The keys of the selected items
inputBehavior
"autohighlight" | "autocomplete" | "none"
Defines the auto-completion behavior of the combobox. - `autohighlight`: The first focused item is highlighted as the user types - `autocomplete`: Navigating the listbox with the arrow keys selects the item and the input is updated
selectionBehavior
"clear" | "replace" | "preserve"
The behavior of the combobox input when an item is selected - `replace`: The selected item string is set as the input value - `clear`: The input value is cleared - `preserve`: The input value is preserved
autoFocus
boolean
Whether to autofocus the input on mount
openOnClick
boolean
Whether to open the combobox popup on initial click on the input
openOnChange
boolean | ((details: InputValueChangeDetails) => boolean)
Whether to show the combobox when the input value changes
allowCustomValue
boolean
Whether to allow typing custom values in the input
loopFocus
boolean
Whether to loop the keyboard navigation through the items
positioning
PositioningOptions
The positioning options to dynamically position the menu
onInputValueChange
(details: InputValueChangeDetails) => void
Function called when the input's value changes
onValueChange
(details: ValueChangeDetails<T>) => void
Function called when a new item is selected
onHighlightChange
(details: HighlightChangeDetails<T>) => void
Function called when an item is highlighted using the pointer or keyboard navigation.
onOpenChange
(details: OpenChangeDetails) => void
Function called when the popup is opened
translations
IntlTranslations
Specifies the localized strings that identifies the accessibility elements and their states
collection
ListCollection<any>
The collection of items
multiple
boolean
Whether to allow multiple selection. **Good to know:** When `multiple` is `true`, the `selectionBehavior` is automatically set to `clear`. It is recommended to render the selected items in a separate container.
closeOnSelect
boolean
Whether to close the combobox when an item is selected.
openOnKeyPress
boolean
Whether to open the combobox on arrow key press
scrollToIndexFn
(details: ScrollToIndexDetails) => void
Function to scroll to a specific index
composite
boolean
Whether the combobox is a composed with other composite widgets like tabs
disableLayer
boolean
Whether to disable registering this a dismissable layer
navigate
(details: NavigateDetails) => void
Function to navigate to the selected item
dir
"ltr" | "rtl"
The document's text/writing direction.
id
string
The unique identifier of the machine.
getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.
onPointerDownOutside
(event: PointerDownOutsideEvent) => void
Function called when the pointer is pressed down outside the component
onFocusOutside
(event: FocusOutsideEvent) => void
Function called when the focus is moved outside the component
onInteractOutside
(event: InteractOutsideEvent) => void
Function called when an interaction happens outside the component

Machine API

The combobox api exposes the following methods:

focused
boolean
Whether the combobox is focused
open
boolean
Whether the combobox is open
inputValue
string
The value of the combobox input
highlightedValue
string
The value of the highlighted item
highlightedItem
V
The highlighted item
setHighlightValue
(value: string) => void
The value of the combobox input
syncSelectedItems
() => void
Function to sync the selected items with the value. Useful when `value` is updated from async sources.
selectedItems
V[]
The selected items
hasSelectedItems
boolean
Whether there's a selected item
value
string[]
The selected item keys
valueAsString
string
The string representation of the selected items
selectValue
(value: string) => void
Function to select a value
setValue
(value: string[]) => void
Function to set the value of the combobox
clearValue
(value?: string) => void
Function to clear the value of the combobox
focus
() => void
Function to focus on the combobox input
setInputValue
(value: string) => void
Function to set the input value of the combobox
getItemState
(props: ItemProps) => ItemState
Returns the state of a combobox item
setOpen
(open: boolean) => void
Function to open or close the combobox
collection
ListCollection<V>
Function to toggle the combobox
setCollection
(collection: ListCollection<V>) => void
Function to set the collection of items
reposition
(options?: Partial<PositioningOptions>) => void
Function to set the positioning options
multiple
boolean
Whether the combobox allows multiple selections
disabled
boolean
Whether the combobox is disabled

Data Attributes

Root

name
desc
data-scope
combobox
data-part
root
data-invalid
Present when invalid
data-readonly
Present when read-only

Label

name
desc
data-scope
combobox
data-part
label
data-readonly
Present when read-only
data-disabled
Present when disabled
data-invalid
Present when invalid
data-focus
Present when focused

Control

name
desc
data-scope
combobox
data-part
control
data-state
"open" | "closed"
data-focus
Present when focused
data-disabled
Present when disabled
data-invalid
Present when invalid

Input

name
desc
data-scope
combobox
data-part
input
data-invalid
Present when invalid
data-state
"open" | "closed"

Trigger

name
desc
data-scope
combobox
data-part
trigger
data-state
"open" | "closed"
data-invalid
Present when invalid
data-readonly
Present when read-only
data-disabled
Present when disabled

Content

name
desc
data-scope
combobox
data-part
content
data-state
"open" | "closed"
data-placement
The placement of the content

ClearTrigger

name
desc
data-scope
combobox
data-part
clear-trigger
data-invalid
Present when invalid

Item

name
desc
data-scope
combobox
data-part
item
data-highlighted
Present when highlighted
data-state
"checked" | "unchecked"
data-disabled
Present when disabled
data-value
The value of the item

ItemText

name
desc
data-scope
combobox
data-part
item-text
data-state
"checked" | "unchecked"
data-disabled
Present when disabled
data-highlighted
Present when highlighted

ItemIndicator

name
desc
data-scope
combobox
data-part
item-indicator
data-state
"checked" | "unchecked"

Accessibility

Keyboard Interaction

name
desc
ArrowDown
When the combobox is closed, opens the listbox and highlights to the first option. When the combobox is open, moves focus to the next option.
ArrowUp
When the combobox is closed, opens the listbox and highlights to the last option. When the combobox is open, moves focus to the previous option.
Home
When the combobox is open, moves focus to the first option.
End
When the combobox is open, moves focus to the last option.
Escape
Closes the listbox.
Enter
Selects the highlighted option and closes the combobox.
Esc
Closes the combobox