Skip to content
HHydro UIdocs
Navigate
ComponentsCombobox
ImplementedForms / selection

Combobox

Combine editable text input with filtered, keyboard-navigable options.

Interactive

Try it

value, filter, autocomplete, busy, loop
OptionsBasicStandardProfessionalNo plans found.Loading plans...
Live previewReady
MarkupStateEvents
<hui-combobox id="playground-combobox" name="plan" value="standard" aria-label="Plan" filter="contains" autocomplete="both">
  <hui-combobox-input placeholder="Search plans"></hui-combobox-input>
  <hui-combobox-trigger aria-label="Show plans">Options</hui-combobox-trigger>
  <hui-combobox-content>
    <hui-combobox-option value="basic">Basic</hui-combobox-option>
    <hui-combobox-option value="standard">Standard</hui-combobox-option>
    <hui-combobox-option value="pro">Professional</hui-combobox-option>
    <hui-combobox-empty>No plans found.</hui-combobox-empty>
    <hui-combobox-status>Loading plans...</hui-combobox-status>
  </hui-combobox-content>
</hui-combobox>
data-state
Waiting for enhancement

No component events yet.

Usage

Use hui-combobox when users need to type a query and choose one or more options from a managed list. Use a select when free-text filtering is not needed. Do not use it as a generic search field without options or as a replacement for a command menu.

Anatomy

  • Root: hui-combobox
  • Input and trigger: hui-combobox-input, hui-combobox-trigger
  • Popup: hui-combobox-content
  • Options and feedback: hui-combobox-option, hui-combobox-empty, hui-combobox-status

Examples

Fixture exampleSource-backed
<section aria-labelledby="combobox-heading">
    <h2 id="combobox-heading">Search plans</h2>
    <form id="combobox-form">
      <hui-combobox id="plan-combobox" name="plan" value="standard" aria-labelledby="combobox-heading" autocomplete="both" loop>
        <hui-combobox-input placeholder="Search plans"></hui-combobox-input>
        <hui-combobox-trigger aria-label="Show plan options"><span aria-hidden="true">&#x25BE;</span></hui-combobox-trigger>
        <hui-combobox-content>
          <hui-combobox-option value="basic">Basic</hui-combobox-option>
          <hui-combobox-option value="standard"><b>Standard</b></hui-combobox-option>
          <hui-combobox-option value="pro" disabled>Professional</hui-combobox-option>
          <hui-combobox-option value="cafe" label="Café">Café</hui-combobox-option>
          <hui-combobox-empty>No matching plans.</hui-combobox-empty>
          <hui-combobox-status>Loading plans…</hui-combobox-status>
        </hui-combobox-content>
      </hui-combobox>

      <h3 id="multi-combobox-heading">Selected plans</h3>
      <hui-combobox id="multi-plan-combobox" name="plans" value="basic,standard" selection="multiple" aria-labelledby="multi-combobox-heading">
        <div data-part="selected-values" role="list" aria-label="Selected plans">
          <div data-part="token" data-value="basic" role="listitem">
            <span>Basic</span>
            <button type="button" data-remove-value aria-label="Remove Basic">&#x00D7;</button>
          </div>
          <div data-part="token" data-value="standard" role="listitem">
            <span>Standard</span>
            <button type="button" data-remove-value aria-label="Remove Standard">&#x00D7;</button>
          </div>
        </div>
        <hui-combobox-input placeholder="Add plans"></hui-combobox-input>
        <hui-combobox-trigger aria-label="Show plan options"><span aria-hidden="true">&#x25BE;</span></hui-combobox-trigger>
        <hui-combobox-content>
          <hui-combobox-option value="basic">Basic</hui-combobox-option>
          <hui-combobox-option value="standard">Standard</hui-combobox-option>
          <hui-combobox-option value="cafe" label="Café">Café</hui-combobox-option>
        </hui-combobox-content>
      </hui-combobox>
    </form>
  </section>

API reference

hui-combobox

AttributeMeaning
valueCommitted option value. Multiple values are comma-separated when selection="multiple".
default-valueSeeds value once when no value is present.
queryCurrent editable query. The selected option label is displayed when the query is not dirty.
default-querySeeds query once when no query is present.
open / default-openControls or seeds popup visibility.
selectionsingle (default) or multiple.
filterauto (prefix), contains, or none. Matching is case- and accent-insensitive.
autocompletelist (default), both, or none; both enables inline completion.
select-on-tabCommits the highlighted option before normal tab navigation.
enter-submitLeaves Enter available for form submission while closed.
loopWraps ArrowUp/ArrowDown highlight movement.
busyExposes consumer-controlled loading state through aria-busy; it does not fetch results.
placement / dirPopup placement and keyboard/layout direction.
name, form, disabled, requiredForm and disabled-state metadata.
idStable base for deterministic content and option IDs. ID-less roots omit generated references.
aria-label, aria-labelledby, aria-describedbyAccessible naming inherited by the input and listbox.

The root exposes data-state="open|closed", data-value, data-query, data-results, and data-empty. It emits hui-open-change and hui-value-change events with { open } and { value } details.

Input, trigger, and content

hui-combobox-input renders a native <input> with role="combobox", aria-expanded, aria-controls, aria-autocomplete, aria-haspopup="listbox", aria-activedescendant, and aria-busy. The input owns keyboard handling and retains focus. hui-combobox-trigger renders a labelled button that toggles the popup without stealing input focus.

hui-combobox-content renders role="listbox", inherits accessible naming, and is hidden while closed. Open content is positioned against the input and portalled when a clipped ancestor would contain it. After opening, page scrolling does not reposition the content; it stays at its last document coordinates, allowing the page to carry it out of the viewport. Opening and viewport resizing still run collision-aware positioning. data-state and data-placement are exposed.

For selection="multiple", consumers may project a selected-values surface. A token should expose its committed value through data-value and contain an accessible button[type="button"][data-remove-value]. The remove attribute may contain the value itself; when empty, the nearest token's data-value is used. The component owns the mutation and emits the existing hui-value-change event; consumers own token labels, layout, and rerendering. Remove controls are ignored for single-selection and disabled roots.

Options, empty, and status

  • hui-combobox-option renders role="option", aria-selected, aria-disabled, a deterministic ID, and data-highlighted.
  • hui-combobox-empty renders an optional role="status" empty-result message.
  • hui-combobox-status renders an optional polite status for loading or result metadata; result announcements also use the shared polite live-region helper.
  • Set busy while the consumer is fetching or replacing options. The input and listbox expose aria-busy="true", and a projected hui-combobox-status is visible only while the popup is open and busy. The component does not start a request or change popup visibility when busy changes.
  • The documentation playground's Show loading results control intentionally opens the popup when enabled so the status can be inspected; this convenience behavior is not part of the component API.
  • Options use value, label, disabled, and optional selected attributes. When value is omitted, option identity falls back to the host id, then the option's positional index; use explicit values for collections whose order can change. Their projected text is the fallback accessible label.

States and styling

StateContract and styling guidance
DefaultClosed, enabled input with the committed label or query visible.
HoverUse --hui-color-interactive-hover for trigger or option feedback.
ActiveUse --hui-color-interactive-active for pressed controls without changing the committed value until selection.
FocusKeep the input or trigger focus ring visible with --hui-color-focus-ring.
DisabledExpose native disabled semantics and use --hui-color-interactive-disabled; no popup interaction is allowed.
ErrorNo built-in validation state; pair aria-invalid="true" with error text and --hui-color-danger.
State channel

Attributes are the source of truth. Style derived state with data-state, ARIA attributes, and the --hui-* token aliases.

Accessibility and keyboard support

  • Typing filters options. input events with isComposing do not filter until composition ends. filter="auto" matches prefixes; contains matches any substring; none keeps every option.
  • Closed ArrowDown/ArrowUp opens and highlights the selected option, or the first/last enabled result. Open arrows move the highlight, skipping disabled or filtered options; loop wraps at the ends.
  • Closed Home/End retain native caret behavior. Open Home/End move the highlight to the first/last enabled result.
  • Open Enter commits the highlighted option. Closed Enter remains available for form submission when enter-submit is present.
  • Escape is staged: clear a non-selected highlight, then close, then restore the committed label/query. Tab commits the highlighted option when select-on-tab is present and always closes without trapping focus.
  • In autocomplete="both", the first prefix match supplies a selected inline completion tail. ArrowRight and Tab accept the completion.
  • Pointer activation selects an option. Pointer-down keeps focus on the input. Outside pointer/focus and Escape dismiss the popup.
  • Multiple selection toggles options without closing and keeps the input ready for another query.
  • When a multiple-selection input is empty, Backspace or Delete removes the last committed value. With a non-empty query, both keys retain native text editing behavior.

SSR and forms

SSR renders the editable input, trigger, and a complete collapsed listbox with resolved option selection. It also renders a hidden form value input and a native datalist twin so the input remains useful without JavaScript. default-value, default-query, and default-open are resolved on the server. Closed content has hidden="hidden"; portal markers are client-only.

Hydration re-renders component-owned template nodes while preserving projected consumer content and host attributes. In an enhanced browser the custom listbox owns suggestions and the input's list association is removed, while the datalist node remains available in the DOM. Without JavaScript, the native input and its datalist remain usable; the custom listbox is hidden and the committed value is available through the hidden form input.

Code

<hui-combobox id="plan" name="plan" aria-label="Plan" filter="contains">
  <hui-combobox-input placeholder="Search plans"></hui-combobox-input>
  <hui-combobox-trigger aria-label="Show plans">Options</hui-combobox-trigger>
  <hui-combobox-content>
    <hui-combobox-option value="basic">Basic</hui-combobox-option>
    <hui-combobox-option value="pro">Professional</hui-combobox-option>
    <hui-combobox-empty>No plans found</hui-combobox-empty>
  </hui-combobox-content>
</hui-combobox>
<hui-combobox id="playground-combobox" name="plan" value="standard" aria-label="Plan" filter="contains" autocomplete="both">
  <hui-combobox-input placeholder="Search plans"></hui-combobox-input>
  <hui-combobox-trigger aria-label="Show plans">Options</hui-combobox-trigger>
  <hui-combobox-content>
    <hui-combobox-option value="basic">Basic</hui-combobox-option>
    <hui-combobox-option value="standard">Standard</hui-combobox-option>
    <hui-combobox-option value="pro">Professional</hui-combobox-option>
    <hui-combobox-empty>No plans found.</hui-combobox-empty>
    <hui-combobox-status>Loading plans...</hui-combobox-status>
  </hui-combobox-content>
</hui-combobox>