Skip to content
Destyler UI Destyler UI Destyler UI

Select

A Select component allows users pick a value from predefined options.

Features

Install

Install the component from your command line.

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

Anatomy

Import all parts and piece them together.

<script setup lang="ts">
import * as select from '@destyler/select'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, useId } from 'vue'
const [state, send] = useMachine(
select.machine({
id: useId(),
}),
)
const api = computed(() => select.connect(state.value, send, normalizeProps))
</script>
<template>
<button v-bind="api.getTriggerProps()"></button>
<div v-bind="api.getPositionerProps()">
<ul v-bind="api.getContentProps()" >
<li v-bind="api.getItemProps({ item: '0' })" >
<span v-bind="api.getItemIndicatorProps({ item: '0' })"></span>
</li>
</ul>
</div>
</template>
import { normalizeProps, useMachine } from '@destyler/react'
import * as select from '@destyler/select'
import { useId } from 'react'
export default function Select() {
const [state, send] = useMachine(
select.machine({
id: useId(),
}),
)
const api = select.connect(state, send, normalizeProps)
return (
<>
<button {...api.getTriggerProps()} ></button>
<div {...api.getPositionerProps()}>
<ul {...api.getContentProps()} >
<li {...api.getItemProps({ item: '0' })} >
<span {...api.getItemIndicatorProps({ item: '0' })} ></span>
</li>
</ul>
</div>
</>
)
}
<script lang="ts">
import * as select from '@destyler/select'
import { normalizeProps, useMachine } from '@destyler/svelte'
const id = $props.id()
const [state, send] = useMachine(
select.machine({
id,
}),
)
const api = $derived(select.connect(state, send, normalizeProps))
</script>
<button {...api.getTriggerProps()} ></button>
<div {...api.getPositionerProps()} >
<ul {...api.getContentProps()} >
<li {...api.getItemProps({ item:'0' })} >
<span {...api.getItemIndicatorProps({ item:'0' })} ></span>
</li>
</ul>
</div>
import * as select from '@destyler/select'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId } from 'solid-js'
export default function Select() {
const [state, send] = useMachine(
select.machine({
id: createUniqueId(),
}),
)
const api = createMemo(() => select.connect(state, send, normalizeProps))
return (
<>
<button {...api().getTriggerProps()}></button>
<div {...api().getPositionerProps()}>
<ul {...api().getContentProps()}>
<li {...api().getItemProps({ item: '0' })} >
<span {...api().getItemIndicatorProps({ item: '0' })}></span>
</li>
</ul>
</div>
</>
)
}

Setting the initial value

To set the initial value of the select, pass the value property to the select destyler’s context.

const [state, send] = useMachine(
select.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(
select.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(
select.machine({
id: useId(),
collection,
}),
)

Usage within a form

To use select within a form, you’ll need to:

  • Pass the name property to the select destyler’s context

  • Render a hidden select element using api.getSelectProps()

<script setup lang="ts">
import * as select from '@destyler/select'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, useId } from 'vue'
const [state, send] = useMachine(
select.machine({
id: useId(),
name: "country",
}),
)
const api = computed(() => select.connect(state.value, send, normalizeProps))
</script>
<template>
<select v-bind="api.getHiddenSelectProps()"></select>
<button v-bind="api.getTriggerProps()"></button>
<div v-bind="api.getPositionerProps()">
<ul v-bind="api.getContentProps()" >
<li v-bind="api.getItemProps({ item: '0' })" >
<span v-bind="api.getItemIndicatorProps({ item: '0' })"></span>
</li>
</ul>
</div>
</template>
import { normalizeProps, useMachine } from '@destyler/react'
import * as select from '@destyler/select'
import { useId } from 'react'
export default function Select() {
const [state, send] = useMachine(
select.machine({
id: useId(),
name: "country",
}),
)
const api = select.connect(state, send, normalizeProps)
return (
<>
<select {...api.getHiddenSelectProps()}></select>
<button {...api.getTriggerProps()} ></button>
<div {...api.getPositionerProps()}>
<ul {...api.getContentProps()} >
<li {...api.getItemProps({ item: '0' })} >
<span {...api.getItemIndicatorProps({ item: '0' })} ></span>
</li>
</ul>
</div>
</>
)
}
<script lang="ts">
import * as select from '@destyler/select'
import { normalizeProps, useMachine } from '@destyler/svelte'
const id = $props.id()
const [state, send] = useMachine(
select.machine({
id,
name: "country",
}),
)
const api = $derived(select.connect(state, send, normalizeProps))
</script>
<select {...api.getHiddenSelectProps()}></select>
<button {...api.getTriggerProps()} ></button>
<div {...api.getPositionerProps()} >
<ul {...api.getContentProps()} >
<li {...api.getItemProps({ item:'0' })} >
<span {...api.getItemIndicatorProps({ item:'0' })} ></span>
</li>
</ul>
</div>
import * as select from '@destyler/select'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId } from 'solid-js'
export default function Select() {
const [state, send] = useMachine(
select.machine({
id: createUniqueId(),
name: "country",
}),
)
const api = createMemo(() => select.connect(state, send, normalizeProps))
return (
<>
<select {...api().getHiddenSelectProps()}></select>
<button {...api().getTriggerProps()}></button>
<div {...api().getPositionerProps()}>
<ul {...api().getContentProps()}>
<li {...api().getItemProps({ item: '0' })} >
<span {...api().getItemIndicatorProps({ item: '0' })}></span>
</li>
</ul>
</div>
</>
)
}

Disabling the select

To disable the select, set the disabled property in the destyler’s context to true.

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

Close on select

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

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

Looping the keyboard navigation

When navigating with the select using the arrow down and up keys, the select stops at the first and last options. If you need want the navigation to loop back to the first or last option, set the loop: true in the destyler’s context.

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

Listening for highlight changes

When an item is highlighted with the pointer or keyboard, use the onHighlightChange to listen for the change and do something with it.

const [state, send] = useMachine(
select.machine({
onHighlightChange(details) {
// details => {
// highlightedValue: string | null,
// highlightedItem: CollectionItem | null
// }
console.log(details)
},
}),
)

Listening for selection changes

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

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

Listening for open and close events

When the select is opened or closed, the onOpenChange callback is called. You can listen for these events and do something with it.

const [state, send] = useMachine(
select.machine({
onOpenChange(details) {
// details => { open: boolean }
console.log("Select opened")
},
}),
)

Styling guide

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

Open and closed state

When the select is open, the trigger and content is given a data-state attribute.

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

Selected state

Items are given a data-state attribute, indicating whether they are selected.

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

Highlighted state

When an item is highlighted, via keyboard navigation or pointer, it is given a data-highlighted attribute.

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

Invalid state

When the select is invalid, the label and trigger is given a data-invalid attribute.

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

Disabled state

When the select is disabled, the trigger and label is given a data-disabled attribute.

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

Empty state

When no option is selected, the trigger is given a data-placeholder-shown attribute.

[data-part="trigger"][data-placeholder-shown] {
/* styles for empty select state */
}

Methods and Properties

Machine Context

The combobox machine exposes the following context properties:

collection
ListCollection<any>
The item collection
ids
Partial<{ root: string; content: string; control: string; trigger: string; clearTrigger: string; label: string; hiddenSelect: string; positioner: string; item(id: string | number): string; itemGroup(id: string | number): string; itemGroupLabel(id: string | number): string; }>
The ids of the elements in the select. Useful for composition.
name
string
The `name` attribute of the underlying select.
form
string
The associate form of the underlying select.
disabled
boolean
Whether the select is disabled
invalid
boolean
Whether the select is invalid
readOnly
boolean
Whether the select is read-only
required
boolean
Whether the select is required
closeOnSelect
boolean
Whether the select should close after an item is selected
onHighlightChange
(details: HighlightChangeDetails<T>) => void
The callback fired when the highlighted item changes.
onValueChange
(details: ValueChangeDetails<T>) => void
The callback fired when the selected item changes.
onOpenChange
(details: OpenChangeDetails) => void
Function called when the popup is opened
positioning
PositioningOptions
The positioning options of the menu.
value
string[]
The keys of the selected items
highlightedValue
string
The key of the highlighted item
loopFocus
boolean
Whether to loop the keyboard navigation through the options
multiple
boolean
Whether to allow multiple selection
open
boolean
Whether the select menu is open
open.controlled
boolean
Whether the select's open state is controlled by the user
scrollToIndexFn
(details: ScrollToIndexDetails) => void
Function to scroll to a specific index
composite
boolean
Whether the select is a composed with other composite widgets like tabs or combobox
deselectable
boolean
Whether the value can be cleared by clicking the selected item. **Note:** this is only applicable for single selection
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 select api exposes the following methods:

focused
boolean
Whether the select is focused
open
boolean
Whether the select is open
empty
boolean
Whether the select value is empty
highlightedValue
string
The value of the highlighted item
highlightedItem
V
The highlighted item
highlightValue
(value: string) => void
Function to highlight a value
selectedItems
V[]
The selected items
hasSelectedItems
boolean
Whether there's a selected option
value
string[]
The selected item keys
valueAsString
string
The string representation of the selected items
selectValue
(value: string) => void
Function to select a value
selectAll
() => void
Function to select all values
setValue
(value: string[]) => void
Function to set the value of the select
clearValue
(value?: string) => void
Function to clear the value of the select. If a value is provided, it will only clear that value, otherwise, it will clear all values.
focus
() => void
Function to focus on the select input
getItemState
(props: ItemProps<any>) => ItemState
Returns the state of a select item
setOpen
(open: boolean) => void
Function to open or close the select
collection
ListCollection<V>
Function to toggle the select
setCollection
(collection: ListCollection<V>) => void
Function to set the collection of items
reposition
(options?: Partial<PositioningOptions>) => void
Function to set the positioning options of the select
multiple
boolean
Whether the select allows multiple selections
disabled
boolean
Whether the select is disabled

Data Attributes

Root

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

Label

name
desc
data-scope
select
data-part
label
data-disabled
Present when disabled
data-invalid
Present when invalid
data-readonly
Present when read-only

Control

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

ValueText

name
desc
data-scope
select
data-part
value-text
data-disabled
Present when disabled
data-invalid
Present when invalid
data-focus
Present when focused

Trigger

name
desc
data-scope
select
data-part
trigger
data-state
"open" | "closed"
data-disabled
Present when disabled
data-invalid
Present when invalid
data-readonly
Present when read-only
data-placement
The placement of the trigger
data-placeholder-shown
Present when placeholder is shown

Indicator

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

Item

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

ItemText

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

ItemIndicator

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

ItemGroup

name
desc
data-scope
select
data-part
item-group
data-disabled
Present when disabled

ClearTrigger

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

Content

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

Accessibility

Keyboard Interaction

name
desc
Space
When focus is on trigger, opens the select and focuses the first selected item. When focus is on the content, selects the highlighted item.
Enter
When focus is on trigger, opens the select and focuses the first selected item. When focus is on content, selects the focused item.
ArrowDown
When focus is on trigger, opens the select. When focus is on content, moves focus to the next item.
ArrowUp
When focus is on trigger, opens the select. When focus is on content, moves focus to the previous item.
Esc
Closes the select and moves focus to trigger.
A-Z a-z
When focus is on trigger, selects the item whose label starts with the typed character. When focus is on the listbox, moves focus to the next item with a label that starts with the typed character.