Skip to content
Home Theme Gallery

Combobox

The custom element is <hui-combobox>.

Overview

A select whose text input filters the options the host rendered, locally and without fetching. Remote filtering stays the host's job; use hui-search-field when the host wants an event per keystroke.

Example

Searchable select
Source
<hui-combobox name="fruit" placeholder="Search fruit" aria-label="Fruit">
<div role="option" data-value="apple">Apple</div>
<div role="option" data-value="apricot">Apricot</div>
<div role="option" data-value="banana">Banana</div>
<div role="option" data-value="cherry">Cherry</div>
</hui-combobox>

The Go template that renders it:

<hui-combobox name="fruit" placeholder="Search fruit" aria-label="Fruit">
{{ range .Fruits }}
<div role="option" data-value="{{ .Value }}">{{ .Label }}</div>
{{ end }}
</hui-combobox>

API

Attributes6
Attributes
NameTypeDefaultDescription
namestring""The form field name. Without one the control submits nothing.
valuestring""The selected option's value. Reflects to the value attribute and is committed to the form.
placeholderstring"Search"Placeholder shown in the input while it is empty.
requiredbooleanfalseMarks the field as required and blocks submission while no option is selected.
openbooleanfalseWhether the option panel is open. Reflects to the open attribute.
disabledbooleanfalseDisables the input and prevents the panel from opening.
Properties6
Properties
NameTypeDefaultDescription
namestring''Reflects to the name attribute.
valuestring''Reflects to the value attribute.
placeholderstring'Search'Reflects to the placeholder attribute.
requiredbooleanfalseReflects to the required attribute.
openbooleanfalseReflects to the open attribute.
disabledbooleanfalseReflects to the disabled attribute.
Events4
Events
NameDescription
inputEmitted on the host after a selection is committed or a selection is cleared. It bubbles and is composed; the internal input's own event is stopped at the boundary.
changeEmitted alongside input when the committed value changes. It bubbles and is composed.
hui-openEmitted when the panel opens, including via the platform popover's light dismiss. It bubbles and is composed.
hui-closeEmitted when the panel closes. It bubbles and is composed.
Slots1
Slots
NameDescription
(default)The options, each a role="option" element with a data-value. A disabled option carries disabled or aria-disabled="true".
CSS custom properties5
CSS custom properties
NameDefaultDescription
--hui-radius-mdcalc(var(--hui-radius, 0.45rem) * 0.8)Corner radius of the input and the panel.
--hui-popoveroklch(1 0 0)Background of the option panel.
--hui-accentoklch(0.96 0.003 325.6)Background of the active option.
--hui-inputoklch(0.62 0.019 323.02)Border of the input, and of the invalid input.
--hui-destructiveoklch(0.56 0.245 27.325)Border of the input while aria-invalid="true".
::part() hooks4
::part() hooks
NameDescription
inputThe internal role="combobox" text input.
panelThe popover holding the listbox.
listboxThe role="listbox" wrapper around the slot.
emptyThe role="status" "No results" message.

States

Preselected and disabled options

A value chosen already, with one option unavailable. Typing filters the list, and a disabled option is never the match.

Preselected and disabled options
Source
<hui-combobox name="fruit" value="banana" placeholder="Fruit" aria-label="Fruit">
<div role="option" data-value="apple">Apple</div>
<div role="option" data-value="banana">Banana</div>
<div role="option" data-value="grape" aria-disabled="true">Grape</div>
</hui-combobox>

Required

required on a combobox: the form does not submit until a value is chosen.

Required
Source
<hui-combobox name="choice" required placeholder="Choose one" aria-label="Choice">
<div role="option" data-value="one">One</div>
<div role="option" data-value="two">Two</div>
</hui-combobox>

Server-side mechanics

The server renders the <hui-combobox> together with its options as light-DOM children: each a role="option" carrying a data-value, and disabled or aria-disabled="true" when it cannot be chosen. It must give the control a name, and the committed option's data-value then submits under that name; with no name the control submits nothing. Filtering over those rendered options is local, so the server does not send suggestions on each keystroke. An hx-swap that replaces the element while its popover is open closes the panel and drops the current query, whereas an out-of-band swap that only appends new options leaves it open and they are filtered on the next keystroke.

Accessibility

  • The input is exposed as a role="combobox" with aria-autocomplete="list", aria-haspopup="listbox" and live aria-expanded, and points at the listbox with aria-controls.
  • The highlighted option is named by aria-activedescendant; each option carries aria-selected.
  • A disabled option is skipped by the arrow keys, so focus never lands on it.
  • When no option matches, the role="status" "No results" message is shown.

Keyboard

Keyboard
KeysAction
ArrowDown, ArrowUpOpen the panel when it is closed; when open, move the highlight to the next or previous enabled option, wrapping at the ends.
Home, EndMove the highlight to the first or last option.
EnterSelect the highlighted option, commit it to the form and close the panel.
EscapeClose the panel without changing the selection.
BackspaceOn an empty input, clear the current selection.
Printable charactersFilter the options by label and highlight the first survivor.

Gotchas

  • The internal input's native input is stopped at the shadow boundary, so the host only ever sees the synthetic input, which fires on commit or clear rather than on every keystroke; local filtering is all the element does, and remote filtering is the host's job.
  • Typing clears the committed selection and commits an empty value, so the field submits nothing again until an option is chosen.