Components

Combobox

Combine text input and option list for filter + commit interactions.

Release Scope: Canonical Implementation: Core Site Visibility: Listed Adapters: React, Vanilla, Solid, Vue, Svelte Category: input

1. Live Preview

vanilla live preview activates on the client; source HTML keeps a readable preview island and primitive contract.

vanilla combobox preview activates on the client.

2. Installation

Install adapter-specific dependencies first, then complete setup via the installation guide. installation docs

pnpm add @nake-ui/primitives @nake-ui/adapter-vanilla @nake-ui/theme-vanilla

3. Usage

Minimal runnable usage for the current adapter.

import "@nake-ui/theme-vanilla/styles.css";
import { bindCombobox } from "@nake-ui/adapter-vanilla";

const binding = bindCombobox(nodes, items, options);
binding.destroy();

4. Examples

Multiple (chips)

Builds chips-style multiple selection on top of `bindCombobox` with custom selected-value state.

Selected options render as chips while input stays query-focused.

import "@nake-ui/theme-vanilla/styles.css";
import { bindCombobox } from "@nake-ui/adapter-vanilla";

const selectedValues: string[] = [];

const binding = bindCombobox(
  { root, input, listbox, options },
  registrations,
  {
    allowCustomValue: false,
    onValueCommit(value, optionId) {
      if (optionId === null) {
        return;
      }

      const index = selectedValues.indexOf(value);

      if (index >= 0) {
        selectedValues.splice(index, 1);
      } else {
        selectedValues.push(value);
      }

      input.value = "";
      binding.controller.setInputValue("");
      binding.sync();
    }
  }
);

input.setAttribute("aria-label", "Search fruits");

Clear Button

A right-side clear button resets query and keeps combobox ready for the next search.

import "@nake-ui/theme-vanilla/styles.css";
import { bindCombobox } from "@nake-ui/adapter-vanilla";

const binding = bindCombobox({ root, input, listbox, options }, registrations);

clearButton.addEventListener("click", () => {
  input.value = "";
  binding.controller.setInputValue("");
  input.focus();
  binding.controller.setOpen(false);
  binding.sync();
});

clearButton.setAttribute("aria-label", "Clear selected option");

Groups

Organizes grouped options with native `role=group` and `role=separator`.

Fruits
Vegetables
import "@nake-ui/theme-vanilla/styles.css";
import { bindCombobox } from "@nake-ui/adapter-vanilla";

const binding = bindCombobox({ root, input, listbox, options }, registrations, {
  onValueCommit() {
    binding.controller.setOpen(false);
    binding.sync();
  }
});

trigger.setAttribute("aria-label", "Open options list");

Invalid

Marks invalid input state via `aria-invalid` and `aria-describedby`.

Please choose a valid fruit option.

import "@nake-ui/theme-vanilla/styles.css";
import { bindCombobox } from "@nake-ui/adapter-vanilla";

const helperId = "combobox-invalid-help";
const binding = bindCombobox({ root, input, listbox, options }, registrations);

input.setAttribute("aria-invalid", "true");
input.setAttribute("aria-describedby", helperId);

helper.textContent = "Please choose a valid fruit option.";
binding.sync();

Disabled

Disables input/trigger so the combobox stays non-interactive.

Disable input and trigger interactions with native disabled attributes.

import "@nake-ui/theme-vanilla/styles.css";
import { bindCombobox } from "@nake-ui/adapter-vanilla";

// Disabled input/trigger keeps the combobox non-interactive.
input.disabled = true;
trigger.disabled = true;
input.placeholder = "Combobox is disabled";

const binding = bindCombobox({ root, input, listbox, options }, registrations);
binding.sync();

Custom Option

Renders rich option content (main text + description) while keeping committed values stable.

import "@nake-ui/theme-vanilla/styles.css";
import { bindCombobox } from "@nake-ui/adapter-vanilla";

const registrations = optionsData.map((option) => ({
  id: option.id,
  label: option.label,
  value: option.value
}));

const binding = bindCombobox({ root, input, listbox, options }, registrations);
input.placeholder = "Pick an item";
binding.sync();

Auto Highlight

With `autoHighlight`, filtering automatically highlights the first match.

First matched option gets highlighted automatically.

import "@nake-ui/theme-vanilla/styles.css";
import { bindCombobox } from "@nake-ui/adapter-vanilla";

const binding = bindCombobox(
  { root, input, listbox, options },
  registrations,
  { autoHighlight: true }
);

input.placeholder = "Type to filter";
binding.sync();

Popup

Uses a button trigger and places the search input inside the popup panel.

import "@nake-ui/theme-vanilla/styles.css";
import { bindCombobox } from "@nake-ui/adapter-vanilla";

const binding = bindCombobox({ root, input, listbox, options }, registrations, {
  closeOnOutsidePointerDown: true
});

trigger.addEventListener("click", () => {
  binding.controller.setOpen(!binding.controller.getOpen());
  binding.sync();
});

trigger.textContent = "Choose item";

Input Group

Adds an input addon prefix without changing combobox semantics.

import "@nake-ui/theme-vanilla/styles.css";
import { bindCombobox } from "@nake-ui/adapter-vanilla";

const binding = bindCombobox({ root, input, listbox, options }, registrations);
addon.textContent = "SKU";
binding.sync();

5. RTL

The same structure running inside a `dir="rtl"` container.

vanilla combobox preview activates on the client.

6. Closure Coverage

Complete

Closure Score: 100% (19/19)

Required Checks

  • schemaRegistered
  • specDocumented
  • reactMapper
  • siteExampleCoverage
  • siteA11yCoverage
  • siteVisible
  • schemaValidatorCoverage
  • schemaAriaCoverage
  • specValidatorRulesSection
  • specTestChecklistSection
  • specSsrHydrationSection
  • reactSsrHydrationCoverage
  • reactRenderLevelSsrCoverage
  • solidSsrCoverage
  • coreImplemented
  • primitiveUnitTest
  • domContractSnapshot
  • vanillaBinding
  • adapterParitySnapshot

All required closure checks are currently satisfied.

7. Purpose / Non-goals

Combine text input and option list for filter + commit interactions.

  • Advanced fuzzy ranking policy
  • Async suggestion fetching policy in core

8. Anatomy & Slot

  • root
  • anchor (optional, recommended)
  • input
  • trigger (optional)
  • positioner (optional)
  • content (optional)
  • empty (optional)
  • listbox
  • option (repeatable)
  • chips (optional, multiple mode)
  • chip (optional, repeatable, inside chips)

9. DOM Contract

<div data-ui="combobox" data-slot="root">
  <div data-slot="anchor">
    <input
      data-slot="input"
      data-state="open"
      role="combobox"
      aria-controls="combo-list"
      aria-expanded="true"
      aria-activedescendant="combo-option-apple"
      value="Apple"
    />
  </div>
  <div data-slot="positioner" data-placement="bottom-start" data-side="bottom">
    <div id="combo-list" data-slot="listbox" data-state="open" role="listbox">
      <button
        id="combo-option-apple"
        data-slot="option"
        data-selected="true"
        data-highlighted="true"
        role="option"
        aria-selected="true"
      >
        Apple
      </button>
      <button id="combo-option-banana" data-slot="option" role="option" aria-selected="false">
        Banana
      </button>
    </div>
  </div>
</div>

10. State & Events

State Model

  • input value controlled/uncontrolled
  • open controlled/uncontrolled
  • highlighted option id
  • selected/committed value
  • committed value is preserved across close, outside dismiss, and reopen
  • reopen restores selected option highlight when no explicit highlight is active

Event Model

  • input updates query, recomputes visible options, and opens listbox
  • mousedown on input opens listbox without mutating query
  • outside pointerdown closes listbox without clearing committed selection
  • mouseenter on option updates highlighted option
  • mousedown on option commits the option value
  • Enter commits highlighted option; when no highlight and allowCustomValue=true, commits current input value
  • Escape closes listbox without committing
  • ArrowUp / ArrowDown / Home / End navigate highlight through enabled options
  • in multiple mode, Enter toggles highlighted value and keeps listbox open for continued selection
  • in multiple mode, Backspace removes the last selected chip only when input is empty and IME composition is inactive

11. Keyboard & Focus

  • focus remains on input during open, navigation, and commit flows
  • aria-activedescendant points to the highlighted option while listbox is open
  • default open state starts with no highlighted option
  • if a value is already selected when reopening, the selected option is reflected as highlighted
  • with autoHighlight=true and non-empty query, first visible enabled option becomes highlighted
  • ArrowUp / ArrowDown / Home / End move highlight without moving DOM focus away from input
  • Enter commits highlighted option (or custom value when allowed); Escape closes listbox
  • in multiple mode, Backspace from an empty input removes the latest selected chip

12. ARIA

  • input: role=combobox, aria-controls, aria-expanded, aria-autocomplete=list
  • input: include aria-activedescendant only when an option is currently highlighted
  • listbox: role=listbox; include aria-multiselectable=true in multiple mode
  • option: role=option, aria-selected, aria-disabled (when disabled)
  • trigger (if rendered): aria-haspopup=listbox, aria-expanded, aria-controls
  • invalid state should be reflected on input via aria-invalid
  • combobox input must have an accessible name (&lt;label for&gt;, aria-label, or aria-labelledby)

13. Validator Rules

  • structure.missing-required-slot
  • structure.invalid-slot-placement
  • aria.missing-label
  • aria.broken-controls-linkage
  • aria.invalid-activedescendant-target
  • state.controlled-uncontrolled-conflict
  • state.invalid-default-value
  • keyboard.missing-required-keymap
  • keyboard.unhandled-escape
  • keyboard.unhandled-typeahead
  • dom.missing-public-data-attr

14. Adapter Notes

Vanilla

  • Primary binder: bindCombobox.
  • Binds explicit DOM nodes and should be cleaned up on unmount.

15. Example Mapping

16. API

Core

  • createComboboxController value
  • ComboboxController type
  • ComboboxControllerOptions type
  • ComboboxKeyboardResult type
  • ComboboxOptionRegistration type

Vanilla

  • bindCombobox function
  • ComboboxBindingOptions interface
  • ComboboxElements interface
  • ComboboxPositioningPlacement type
  • ComboboxPositioningReason type
  • ComboboxPositioningRequest interface