Skip to content

Combobox

Experimental Alpha

This library is a work-in-progress. We are releasing it early to gather feedback, but it is not ready for production.

An accessible autocomplete widget that combines a text input with a filterable dropdown list. Perfect for searchable select inputs with keyboard navigation support.

Examples

Vue
Lit
React
Live Preview

Basic Combobox

Selected: None

Sizes

Filter Modes

Clearable

Selected: ca

States

With Help Text

Loading State

With Disabled Options

Custom No Results Text

Hidden Label

Monochrome Variant (Single Select)

Multiple Select

Selected:

Multiple with Defaults

Multiple Clearable

Multiple with Max Visible Tags

Multiple Monochrome

Event Handling

External Label Support

The Combobox component supports external labels with helper text, required fields, and validation states.

Label Positioning

Control label position with label-position: 'top' (default), 'start', 'end', or 'bottom'. Note: 'bottom' is not recommended as the dropdown will cover the label when opened.

View Vue Code
<template>
  <section>
    <div class="mbe4">
      <h2>Basic Combobox</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="selectedState"
        :options="stateOptions"
        label="Select State"
        placeholder="Choose a state..."
        class="mbe2"
        @change="handleStateChange"
      />
      <p class="text-sm text-secondary">Selected: {{ selectedState || 'None' }}</p>
    </div>

    <div class="mbe4">
      <h2>Sizes</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="sizeSmall"
        :options="stateOptions"
        label="Small Combobox"
        size="small"
        placeholder="Small size"
        class="mbe2"
      />
      <VueCombobox
        v-model:value="sizeDefault"
        :options="stateOptions"
        label="Default Combobox"
        size="default"
        placeholder="Default size"
        class="mbe2"
      />
      <VueCombobox
        v-model:value="sizeLarge"
        :options="stateOptions"
        label="Large Combobox"
        size="large"
        placeholder="Large size"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>Filter Modes</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="filterStartsWith"
        :options="stateOptions"
        label="Starts With Filter"
        filter-mode="startsWith"
        placeholder="Type to filter..."
        help-text="Try typing 'Ca' to see California"
        class="mbe2"
      />
      <VueCombobox
        v-model:value="filterContains"
        :options="stateOptions"
        label="Contains Filter"
        filter-mode="contains"
        placeholder="Type to search..."
        help-text="Try typing 'or' to see multiple matches"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>Clearable</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="clearableValue"
        :options="stateOptions"
        label="Clearable Combobox"
        :clearable="true"
        placeholder="Select and clear..."
        help-text="Clear button appears when a value is selected"
        class="mbe2"
      />
      <p class="text-sm text-secondary">Selected: {{ clearableValue || 'None' }}</p>
    </div>

    <div class="mbe4">
      <h2>States</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="stateDisabled"
        :options="stateOptions"
        label="Disabled Combobox"
        :disabled="true"
        placeholder="This is disabled"
        class="mbe2"
      />
      <VueCombobox
        v-model:value="stateReadonly"
        :options="stateOptions"
        label="Readonly Combobox"
        :readonly="true"
        placeholder="This is readonly"
        class="mbe2"
      />
      <VueCombobox
        v-model:value="stateRequired"
        :options="stateOptions"
        label="Required Combobox"
        :required="true"
        placeholder="This is required"
        help-text="This field is required"
        class="mbe2"
      />
      <VueCombobox
        v-model:value="stateInvalid"
        :options="stateOptions"
        label="Invalid Combobox"
        :invalid="true"
        error-message="Please select a valid state"
        placeholder="This has an error"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>With Help Text</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="helpTextValue"
        :options="stateOptions"
        label="State of Residence"
        placeholder="Select your state..."
        help-text="Choose the state where you currently reside"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>Loading State</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="loadingValue"
        :options="loadingOptions"
        label="Loading Combobox"
        :loading="isLoading"
        loading-text="Fetching states..."
        placeholder="Loading..."
        class="mbe2"
      />
      <button
        @click="toggleLoading"
        class="btn btn-primary"
      >
        {{ isLoading ? 'Stop Loading' : 'Start Loading' }}
      </button>
    </div>

    <div class="mbe4">
      <h2>With Disabled Options</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="disabledOptionsValue"
        :options="optionsWithDisabled"
        label="Select State"
        placeholder="Some options are disabled..."
        help-text="Colorado and Florida are disabled"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>Custom No Results Text</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="customNoResults"
        :options="stateOptions"
        label="Search States"
        placeholder="Type to search..."
        no-results-text="No states match your search"
        help-text="Try typing 'xyz' to see custom no results message"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>Hidden Label</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="hiddenLabelValue"
        :options="stateOptions"
        label="State (Hidden)"
        :label-hidden="true"
        placeholder="Label is visually hidden but accessible"
        help-text="The label is hidden visually but still accessible to screen readers"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>Monochrome Variant (Single Select)</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="monochromeValue"
        :options="stateOptions"
        label="Select State"
        variant="monochrome"
        placeholder="Choose a state..."
        help-text="Monochrome variant with inverted colors for selected items"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>Multiple Select</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="multipleValue"
        :options="stateOptions"
        label="Select States"
        :multiple="true"
        :close-on-select="false"
        placeholder="Choose one or more states..."
        help-text="Select multiple states with checkboxes"
        class="mbe2"
      />
      <p class="text-sm text-secondary">Selected: {{ Array.isArray(multipleValue) ? multipleValue.join(', ') : 'None' }}</p>
    </div>

    <div class="mbe4">
      <h2>Multiple with Defaults</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        :options="stateOptions"
        label="Pre-selected States"
        :multiple="true"
        :close-on-select="false"
        :default-value="['ca', 'fl']"
        help-text="California and Florida are pre-selected"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>Multiple Clearable</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="multipleClearable"
        :options="stateOptions"
        label="Select States"
        :multiple="true"
        :clearable="true"
        :close-on-select="false"
        placeholder="Select and clear multiple..."
        help-text="Clear button removes all selections"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>Multiple with Max Visible Tags</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        :options="stateOptions"
        label="Select States"
        :multiple="true"
        :close-on-select="false"
        :default-value="['al', 'ak', 'az', 'ar', 'ca']"
        :max-options-visible="2"
        help-text="Shows only 2 tags, with a +3 summary"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>Multiple Monochrome</h2>
    </div>
    <div class="stacked mbe4">
      <VueCombobox
        v-model:value="multipleMonochrome"
        :options="stateOptions"
        label="Select States"
        :multiple="true"
        variant="monochrome"
        :close-on-select="false"
        placeholder="Choose one or more states..."
        help-text="Multiple select with monochrome variant"
        class="mbe2"
      />
    </div>

    <div class="mbe4">
      <h2>Event Handling</h2>
    </div>
    <div class="stacked mbe4">
      <div
        class="flex-inline items-center"
        :style="{gap: '10px'}"
      >
        <VueCombobox
          v-model:value="eventTestValue"
          :options="stateOptions"
          label="Event Test"
          placeholder="Type or select..."
          @change="handleChange"
          @select="handleSelect"
          @search="handleSearch"
          class="mbe2"
        />
        <p v-if="lastEvent">
          Last event: <strong>{{ lastEvent }}</strong>
        </p>
      </div>
    </div>

    <div class="mbe4">
      <h2>External Label Support</h2>
      <p class="mbs2 mbe3">
        The Combobox component supports external labels with helper text, required fields, and validation states.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 600px;"
    >
      <div class="mbe3">
        <VueCombobox
          :options="stateOptions"
          label="Favorite State"
          name="favorite"
          placeholder="Choose a state..."
        />
      </div>

      <div class="mbe3">
        <VueCombobox
          :options="stateOptions"
          label="Home State"
          help-text="Select the state where you currently reside"
          name="home"
          placeholder="Choose your home state..."
        />
      </div>

      <div class="mbe3">
        <VueCombobox
          :options="stateOptions"
          label="Required State"
          :required="true"
          help-text="This field is required"
          name="required"
          placeholder="Choose a state..."
        />
      </div>

      <div class="mbe3">
        <VueCombobox
          :options="stateOptions"
          label="State Selection"
          :required="true"
          :invalid="true"
          error-message="Please select a valid state to continue"
          name="validation"
          placeholder="Choose a state..."
        />
      </div>
    </div>

    <div class="mbe4">
      <h2>Label Positioning</h2>
      <p class="mbs2 mbe3">
        Control label position with <code>label-position</code>: 'top' (default), 'start', 'end', or 'bottom'.
        <strong>Note:</strong> 'bottom' is not recommended as the dropdown will cover the label when opened.
      </p>
    </div>
    <div
      class="mbe4"
      style="max-width: 600px;"
    >
      <div class="mbe3">
        <VueCombobox
          :options="stateOptions"
          label="Top Label (Default)"
          label-position="top"
          name="pos-top"
          placeholder="Choose a state..."
        />
      </div>

      <div class="mbe3">
        <VueCombobox
          :options="stateOptions"
          label="Start Position"
          label-position="start"
          name="pos-start"
          placeholder="Choose a state..."
        />
      </div>

      <div class="mbe3">
        <VueCombobox
          :options="stateOptions"
          label="End Position"
          label-position="end"
          name="pos-end"
          placeholder="Choose a state..."
        />
      </div>

      <div class="mbe3">
        <VueCombobox
          :options="stateOptions"
          label="Bottom Position (Not Recommended)"
          label-position="bottom"
          name="pos-bottom"
          help-text="The dropdown will cover this label when opened"
          placeholder="Choose a state..."
        />
      </div>
    </div>
  </section>
</template>

<script setup lang="ts">
import { ref } from "vue";
import VueCombobox from "agnosticui-core/combobox/vue";
import type { ComboboxOption } from "agnosticui-core/combobox";

// State options
const stateOptions: ComboboxOption[] = [
  { value: "al", label: "Alabama" },
  { value: "ak", label: "Alaska" },
  { value: "az", label: "Arizona" },
  { value: "ar", label: "Arkansas" },
  { value: "ca", label: "California" },
  { value: "co", label: "Colorado" },
  { value: "ct", label: "Connecticut" },
  { value: "de", label: "Delaware" },
  { value: "fl", label: "Florida" },
  { value: "ga", label: "Georgia" },
];

// Options with some disabled
const optionsWithDisabled: ComboboxOption[] = [
  { value: "ca", label: "California" },
  { value: "co", label: "Colorado", disabled: true },
  { value: "ct", label: "Connecticut" },
  { value: "fl", label: "Florida", disabled: true },
  { value: "ny", label: "New York" },
];

// Basic
const selectedState = ref("");

// Sizes
const sizeSmall = ref("");
const sizeDefault = ref("");
const sizeLarge = ref("");

// Filter modes
const filterStartsWith = ref("");
const filterContains = ref("");

// Clearable
const clearableValue = ref("ca");

// States
const stateDisabled = ref("ca");
const stateReadonly = ref("co");
const stateRequired = ref("");
const stateInvalid = ref("");

// Help text
const helpTextValue = ref("");

// Loading
const loadingValue = ref("");
const loadingOptions = ref<ComboboxOption[]>([]);
const isLoading = ref(false);

// Disabled options
const disabledOptionsValue = ref("");

// Custom no results
const customNoResults = ref("");

// Hidden label
const hiddenLabelValue = ref("");

// Monochrome
const monochromeValue = ref("");

// Multiple select
const multipleValue = ref<string[]>([]);
const multipleClearable = ref<string[]>(["ny", "co"]);
const multipleMonochrome = ref<string[]>([]);

// Event handling
const eventTestValue = ref("");
const lastEvent = ref<string | null>(null);
const lastSelectedValue = ref<string | null>(null);

const handleChange = (detail: {
  value: string;
  option: ComboboxOption | null;
}) => {
  lastSelectedValue.value = detail.value;
  const optionLabel = detail.option ? detail.option.label : "cleared";
  lastEvent.value = `change (value: ${
    detail.value || "none"
  }, option: ${optionLabel})`;
};

const handleSelect = (detail: { option: ComboboxOption; value: string }) => {
  lastSelectedValue.value = detail.value;
  lastEvent.value = `select (value: ${detail.value}, label: ${detail.option.label})`;
};

const handleSearch = (detail: { searchTerm: string }) => {
  const selectedInfo = lastSelectedValue.value
    ? `, selected: ${lastSelectedValue.value}`
    : "";
  lastEvent.value = `search (searchTerm: "${detail.searchTerm}"${selectedInfo})`;
};

// Basic state change handler
const handleStateChange = (detail: {
  value: string;
  option: ComboboxOption | null;
}) => {
  console.log("State changed:", detail);
};

// Toggle loading
const toggleLoading = () => {
  isLoading.value = !isLoading.value;
  if (isLoading.value) {
    loadingOptions.value = [];
    // Simulate async loading
    setTimeout(() => {
      loadingOptions.value = stateOptions;
      isLoading.value = false;
    }, 2000);
  } else {
    loadingOptions.value = stateOptions;
  }
};
</script>

<style scoped>
.stacked > * + * {
  margin-top: 1rem;
}

.text-sm {
  font-size: 0.875rem;
}

.text-secondary {
  color: var(--ag-text-secondary);
}

.btn {
  padding: var(--ag-space-2) var(--ag-space-4);
  border-radius: var(--ag-radius-md);
  border: none;
  cursor: pointer;
  font-size: var(--ag-font-size-base);
}

.btn-primary {
  background-color: var(--ag-primary);
  color: white;
}

.btn-primary:hover {
  opacity: 0.9;
}
</style>
Live Preview
View Lit / Web Component Code
import { LitElement, html } from 'lit';
import 'agnosticui-core/combobox';

export class ComboboxLitExamples extends LitElement {
  // Render in light DOM to access global utility classes
  createRenderRoot() {
    return this;
  }

  constructor() {
    super();

    // State options
    this.stateOptions = [
      { value: 'al', label: 'Alabama' },
      { value: 'ak', label: 'Alaska' },
      { value: 'az', label: 'Arizona' },
      { value: 'ar', label: 'Arkansas' },
      { value: 'ca', label: 'California' },
      { value: 'co', label: 'Colorado' },
      { value: 'ct', label: 'Connecticut' },
      { value: 'de', label: 'Delaware' },
      { value: 'fl', label: 'Florida' },
      { value: 'ga', label: 'Georgia' },
    ];

    // Options with some disabled
    this.optionsWithDisabled = [
      { value: 'ca', label: 'California' },
      { value: 'co', label: 'Colorado', disabled: true },
      { value: 'ct', label: 'Connecticut' },
      { value: 'fl', label: 'Florida', disabled: true },
      { value: 'ny', label: 'New York' },
    ];

    // State values
    this.selectedState = '';
    this.isLoading = false;
    this.lastEvent = null;
  }

  firstUpdated() {
    // Set options on combobox elements
    const comboboxes = this.querySelectorAll('ag-combobox');
    comboboxes.forEach((combobox) => {
      const comboId = combobox.id;

      // Set appropriate options based on combobox ID
      if (comboId === 'disabled-options') {
        combobox.options = this.optionsWithDisabled;
      } else {
        combobox.options = this.stateOptions;
      }

      // Add event listeners
      combobox.addEventListener('change', (e) => {
        if (comboId === 'basic-combobox') {
          this.selectedState = e.detail.value;
          this.requestUpdate();
        }
      });
    });
  }

  toggleLoading() {
    this.isLoading = !this.isLoading;
    const loadingCombobox = this.querySelector('#loading-combobox');

    if (loadingCombobox) {
      if (this.isLoading) {
        loadingCombobox.options = [];
        setTimeout(() => {
          loadingCombobox.options = this.stateOptions;
          this.isLoading = false;
          this.requestUpdate();
        }, 2000);
      } else {
        loadingCombobox.options = this.stateOptions;
      }
    }
    this.requestUpdate();
  }

  render() {
    return html`
      <section>
        <!-- Basic Combobox -->
        <div class="mbe4">
          <h2>Basic Combobox</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            id="basic-combobox"
            label="Select State"
            placeholder="Choose a state..."
            class="mbe2"
          ></ag-combobox>
          <p class="text-sm text-secondary">Selected: ${this.selectedState || 'None'}</p>
        </div>

        <!-- Sizes -->
        <div class="mbe4">
          <h2>Sizes</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="Small Combobox"
            size="small"
            placeholder="Small size"
            class="mbe2"
          ></ag-combobox>
          <ag-combobox
            label="Default Combobox"
            size="default"
            placeholder="Default size"
            class="mbe2"
          ></ag-combobox>
          <ag-combobox
            label="Large Combobox"
            size="large"
            placeholder="Large size"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- Filter Modes -->
        <div class="mbe4">
          <h2>Filter Modes</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="Starts With Filter"
            filter-mode="startsWith"
            placeholder="Type to filter..."
            help-text="Try typing 'Ca' to see California"
            class="mbe2"
          ></ag-combobox>
          <ag-combobox
            label="Contains Filter"
            filter-mode="contains"
            placeholder="Type to search..."
            help-text="Try typing 'or' to see multiple matches"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- Clearable -->
        <div class="mbe4">
          <h2>Clearable</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="Clearable Combobox"
            clearable
            placeholder="Select and clear..."
            help-text="Clear button appears when a value is selected"
            value="ca"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- States -->
        <div class="mbe4">
          <h2>States</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="Disabled Combobox"
            disabled
            placeholder="This is disabled"
            value="ca"
            class="mbe2"
          ></ag-combobox>
          <ag-combobox
            label="Readonly Combobox"
            readonly
            placeholder="This is readonly"
            value="co"
            class="mbe2"
          ></ag-combobox>
          <ag-combobox
            label="Required Combobox"
            required
            placeholder="This is required"
            help-text="This field is required"
            class="mbe2"
          ></ag-combobox>
          <ag-combobox
            label="Invalid Combobox"
            invalid
            error-message="Please select a valid state"
            placeholder="This has an error"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- With Help Text -->
        <div class="mbe4">
          <h2>With Help Text</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="State of Residence"
            placeholder="Select your state..."
            help-text="Choose the state where you currently reside"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- Loading State -->
        <div class="mbe4">
          <h2>Loading State</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            id="loading-combobox"
            label="Loading Combobox"
            ?loading=${this.isLoading}
            loading-text="Fetching states..."
            placeholder="Loading..."
            class="mbe2"
          ></ag-combobox>
          <button
            @click=${this.toggleLoading}
            class="btn btn-primary"
          >
            ${this.isLoading ? 'Stop Loading' : 'Start Loading'}
          </button>
        </div>

        <!-- With Disabled Options -->
        <div class="mbe4">
          <h2>With Disabled Options</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            id="disabled-options"
            label="Select State"
            placeholder="Some options are disabled..."
            help-text="Colorado and Florida are disabled"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- Custom No Results Text -->
        <div class="mbe4">
          <h2>Custom No Results Text</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="Search States"
            placeholder="Type to search..."
            no-results-text="No states match your search"
            help-text="Try typing 'xyz' to see custom no results message"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- Hidden Label -->
        <div class="mbe4">
          <h2>Hidden Label</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="State (Hidden)"
            label-hidden
            placeholder="Label is visually hidden but accessible"
            help-text="The label is hidden visually but still accessible to screen readers"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- Monochrome Variant -->
        <div class="mbe4">
          <h2>Monochrome Variant (Single Select)</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="Select State"
            variant="monochrome"
            placeholder="Choose a state..."
            help-text="Monochrome variant with inverted colors for selected items"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- Multiple Select -->
        <div class="mbe4">
          <h2>Multiple Select</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="Select States"
            multiple
            placeholder="Choose one or more states..."
            help-text="Select multiple states with checkboxes"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- Multiple with Defaults -->
        <div class="mbe4">
          <h2>Multiple with Defaults</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="Pre-selected States"
            multiple
            default-value='["ca", "fl"]'
            help-text="California and Florida are pre-selected"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- Multiple Clearable -->
        <div class="mbe4">
          <h2>Multiple Clearable</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="Select States"
            multiple
            clearable
            placeholder="Select and clear multiple..."
            help-text="Clear button removes all selections"
            default-value='["ny", "co"]'
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- Multiple with Max Visible Tags -->
        <div class="mbe4">
          <h2>Multiple with Max Visible Tags</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="Select States"
            multiple
            default-value='["al", "ak", "az", "ar", "ca"]'
            max-options-visible="2"
            help-text="Shows only 2 tags, with a +3 summary"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- Multiple Monochrome -->
        <div class="mbe4">
          <h2>Multiple Monochrome</h2>
        </div>
        <div class="stacked mbe4">
          <ag-combobox
            label="Select States"
            multiple
            variant="monochrome"
            placeholder="Choose one or more states..."
            help-text="Multiple select with monochrome variant"
            class="mbe2"
          ></ag-combobox>
        </div>

        <!-- External Label Support -->
        <div class="mbe4">
          <h2>External Label Support</h2>
          <p class="mbs2 mbe3">
            The Combobox component supports external labels with helper text, required fields, and validation states.
          </p>
        </div>
        <div class="mbe4" style="max-width: 600px;">
          <div class="mbe3">
            <ag-combobox
              label="Favorite State"
              name="favorite"
              placeholder="Choose a state..."
            ></ag-combobox>
          </div>

          <div class="mbe3">
            <ag-combobox
              label="Home State"
              help-text="Select the state where you currently reside"
              name="home"
              placeholder="Choose your home state..."
            ></ag-combobox>
          </div>

          <div class="mbe3">
            <ag-combobox
              label="Required State"
              required
              help-text="This field is required"
              name="required"
              placeholder="Choose a state..."
            ></ag-combobox>
          </div>

          <div class="mbe3">
            <ag-combobox
              label="State Selection"
              required
              invalid
              error-message="Please select a valid state to continue"
              name="validation"
              placeholder="Choose a state..."
            ></ag-combobox>
          </div>
        </div>

        <!-- Label Positioning -->
        <div class="mbe4">
          <h2>Label Positioning</h2>
          <p class="mbs2 mbe3">
            Control label position with <code>label-position</code>: 'top' (default), 'start', 'end', or 'bottom'.
            <strong>Note:</strong> 'bottom' is not recommended as the dropdown will cover the label when opened.
          </p>
        </div>
        <div class="mbe4" style="max-width: 600px;">
          <div class="mbe3">
            <ag-combobox
              label="Top Label (Default)"
              label-position="top"
              name="pos-top"
              placeholder="Choose a state..."
            ></ag-combobox>
          </div>

          <div class="mbe3">
            <ag-combobox
              label="Start Position"
              label-position="start"
              name="pos-start"
              placeholder="Choose a state..."
            ></ag-combobox>
          </div>

          <div class="mbe3">
            <ag-combobox
              label="End Position"
              label-position="end"
              name="pos-end"
              placeholder="Choose a state..."
            ></ag-combobox>
          </div>

          <div class="mbe3">
            <ag-combobox
              label="Bottom Position (Not Recommended)"
              label-position="bottom"
              name="pos-bottom"
              help-text="The dropdown will cover this label when opened"
              placeholder="Choose a state..."
            ></ag-combobox>
          </div>
        </div>
      </section>
    `;
  }
}

// Register the custom element
customElements.define('combobox-lit-examples', ComboboxLitExamples);

Interactive Preview: Click the "Open in StackBlitz" button below to see this example running live in an interactive playground.

View React Code
import { useState } from "react";
import { ReactCombobox } from "agnosticui-core/combobox/react";

export default function ComboboxReactExamples() {
  // State options
  const stateOptions = [
    { value: "al", label: "Alabama" },
    { value: "ak", label: "Alaska" },
    { value: "az", label: "Arizona" },
    { value: "ar", label: "Arkansas" },
    { value: "ca", label: "California" },
    { value: "co", label: "Colorado" },
    { value: "ct", label: "Connecticut" },
    { value: "de", label: "Delaware" },
    { value: "fl", label: "Florida" },
    { value: "ga", label: "Georgia" },
  ];

  // Options with some disabled
  const optionsWithDisabled = [
    { value: "ca", label: "California" },
    { value: "co", label: "Colorado", disabled: true },
    { value: "ct", label: "Connecticut" },
    { value: "fl", label: "Florida", disabled: true },
    { value: "ny", label: "New York" },
  ];

  // State values
  const [selectedState, setSelectedState] = useState("");
  const [isLoading, setIsLoading] = useState(false);
  const [loadingOptions, setLoadingOptions] = useState([]);
  const [lastEvent, setLastEvent] = useState(null);

  const handleStateChange = (detail) => {
    setSelectedState(detail.value);
    console.log("State changed:", detail);
  };

  const toggleLoading = () => {
    setIsLoading(!isLoading);
    if (!isLoading) {
      setLoadingOptions([]);
      // Simulate async loading
      setTimeout(() => {
        setLoadingOptions(stateOptions);
        setIsLoading(false);
      }, 2000);
    } else {
      setLoadingOptions(stateOptions);
    }
  };

  const handleChange = (detail) => {
    const optionLabel = detail.option ? detail.option.label : "cleared";
    setLastEvent(`change (value: ${detail.value || "none"}, option: ${optionLabel})`);
  };

  const handleSelect = (detail) => {
    setLastEvent(`select (value: ${detail.value}, label: ${detail.option.label})`);
  };

  const handleSearch = (detail) => {
    setLastEvent(`search (searchTerm: "${detail.searchTerm}")`);
  };

  return (
    <section>
      {/* Basic Combobox */}
      <div className="mbe4">
        <h2>Basic Combobox</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Select State"
          placeholder="Choose a state..."
          className="mbe2"
          onChange={handleStateChange}
        />
        <p className="text-sm text-secondary">Selected: {selectedState || 'None'}</p>
      </div>

      {/* Sizes */}
      <div className="mbe4">
        <h2>Sizes</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Small Combobox"
          size="small"
          placeholder="Small size"
          className="mbe2"
        />
        <ReactCombobox
          options={stateOptions}
          label="Default Combobox"
          size="default"
          placeholder="Default size"
          className="mbe2"
        />
        <ReactCombobox
          options={stateOptions}
          label="Large Combobox"
          size="large"
          placeholder="Large size"
          className="mbe2"
        />
      </div>

      {/* Filter Modes */}
      <div className="mbe4">
        <h2>Filter Modes</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Starts With Filter"
          filterMode="startsWith"
          placeholder="Type to filter..."
          helpText="Try typing 'Ca' to see California"
          className="mbe2"
        />
        <ReactCombobox
          options={stateOptions}
          label="Contains Filter"
          filterMode="contains"
          placeholder="Type to search..."
          helpText="Try typing 'or' to see multiple matches"
          className="mbe2"
        />
      </div>

      {/* Clearable */}
      <div className="mbe4">
        <h2>Clearable</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Clearable Combobox"
          clearable
          placeholder="Select and clear..."
          helpText="Clear button appears when a value is selected"
          value="ca"
          className="mbe2"
        />
      </div>

      {/* States */}
      <div className="mbe4">
        <h2>States</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Disabled Combobox"
          disabled
          placeholder="This is disabled"
          value="ca"
          className="mbe2"
        />
        <ReactCombobox
          options={stateOptions}
          label="Readonly Combobox"
          readonly
          placeholder="This is readonly"
          value="co"
          className="mbe2"
        />
        <ReactCombobox
          options={stateOptions}
          label="Required Combobox"
          required
          placeholder="This is required"
          helpText="This field is required"
          className="mbe2"
        />
        <ReactCombobox
          options={stateOptions}
          label="Invalid Combobox"
          invalid
          errorMessage="Please select a valid state"
          placeholder="This has an error"
          className="mbe2"
        />
      </div>

      {/* With Help Text */}
      <div className="mbe4">
        <h2>With Help Text</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="State of Residence"
          placeholder="Select your state..."
          helpText="Choose the state where you currently reside"
          className="mbe2"
        />
      </div>

      {/* Loading State */}
      <div className="mbe4">
        <h2>Loading State</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={loadingOptions}
          label="Loading Combobox"
          loading={isLoading}
          loadingText="Fetching states..."
          placeholder="Loading..."
          className="mbe2"
        />
        <button
          onClick={toggleLoading}
          className="btn btn-primary"
        >
          {isLoading ? 'Stop Loading' : 'Start Loading'}
        </button>
      </div>

      {/* With Disabled Options */}
      <div className="mbe4">
        <h2>With Disabled Options</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={optionsWithDisabled}
          label="Select State"
          placeholder="Some options are disabled..."
          helpText="Colorado and Florida are disabled"
          className="mbe2"
        />
      </div>

      {/* Custom No Results Text */}
      <div className="mbe4">
        <h2>Custom No Results Text</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Search States"
          placeholder="Type to search..."
          noResultsText="No states match your search"
          helpText="Try typing 'xyz' to see custom no results message"
          className="mbe2"
        />
      </div>

      {/* Hidden Label */}
      <div className="mbe4">
        <h2>Hidden Label</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="State (Hidden)"
          labelHidden
          placeholder="Label is visually hidden but accessible"
          helpText="The label is hidden visually but still accessible to screen readers"
          className="mbe2"
        />
      </div>

      {/* Monochrome Variant */}
      <div className="mbe4">
        <h2>Monochrome Variant (Single Select)</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Select State"
          variant="monochrome"
          placeholder="Choose a state..."
          helpText="Monochrome variant with inverted colors for selected items"
          className="mbe2"
        />
      </div>

      {/* Multiple Select */}
      <div className="mbe4">
        <h2>Multiple Select</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Select States"
          multiple
          closeOnSelect={false}
          placeholder="Choose one or more states..."
          helpText="Select multiple states with checkboxes"
          className="mbe2"
        />
      </div>

      {/* Multiple with Defaults */}
      <div className="mbe4">
        <h2>Multiple with Defaults</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Pre-selected States"
          multiple
          closeOnSelect={false}
          defaultValue={["ca", "fl"]}
          helpText="California and Florida are pre-selected"
          className="mbe2"
        />
      </div>

      {/* Multiple Clearable */}
      <div className="mbe4">
        <h2>Multiple Clearable</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Select States"
          multiple
          clearable
          closeOnSelect={false}
          defaultValue={["ny", "co"]}
          placeholder="Select and clear multiple..."
          helpText="Clear button removes all selections"
          className="mbe2"
        />
      </div>

      {/* Multiple with Max Visible Tags */}
      <div className="mbe4">
        <h2>Multiple with Max Visible Tags</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Select States"
          multiple
          closeOnSelect={false}
          defaultValue={["al", "ak", "az", "ar", "ca"]}
          maxOptionsVisible={2}
          helpText="Shows only 2 tags, with a +3 summary"
          className="mbe2"
        />
      </div>

      {/* Multiple Monochrome */}
      <div className="mbe4">
        <h2>Multiple Monochrome</h2>
      </div>
      <div className="stacked mbe4">
        <ReactCombobox
          options={stateOptions}
          label="Select States"
          multiple
          variant="monochrome"
          closeOnSelect={false}
          placeholder="Choose one or more states..."
          helpText="Multiple select with monochrome variant"
          className="mbe2"
        />
      </div>

      {/* Event Handling */}
      <div className="mbe4">
        <h2>Event Handling</h2>
      </div>
      <div className="stacked mbe4">
        <div className="flex-inline items-center" style={{ gap: "10px" }}>
          <ReactCombobox
            options={stateOptions}
            label="Event Test"
            placeholder="Type or select..."
            onChange={handleChange}
            onSelect={handleSelect}
            onSearch={handleSearch}
            className="mbe2"
          />
          {lastEvent && (
            <p>
              Last event: <strong>{lastEvent}</strong>
            </p>
          )}
        </div>
      </div>

      {/* External Label Support */}
      <div className="mbe4">
        <h2>External Label Support</h2>
        <p className="mbs2 mbe3">
          The Combobox component supports external labels with helper text, required fields, and validation states.
        </p>
      </div>
      <div className="mbe4" style={{ maxWidth: "600px" }}>
        <div className="mbe3">
          <ReactCombobox
            options={stateOptions}
            label="Favorite State"
            name="favorite"
            placeholder="Choose a state..."
          />
        </div>

        <div className="mbe3">
          <ReactCombobox
            options={stateOptions}
            label="Home State"
            helpText="Select the state where you currently reside"
            name="home"
            placeholder="Choose your home state..."
          />
        </div>

        <div className="mbe3">
          <ReactCombobox
            options={stateOptions}
            label="Required State"
            required
            helpText="This field is required"
            name="required"
            placeholder="Choose a state..."
          />
        </div>

        <div className="mbe3">
          <ReactCombobox
            options={stateOptions}
            label="State Selection"
            required
            invalid
            errorMessage="Please select a valid state to continue"
            name="validation"
            placeholder="Choose a state..."
          />
        </div>
      </div>

      {/* Label Positioning */}
      <div className="mbe4">
        <h2>Label Positioning</h2>
        <p className="mbs2 mbe3">
          Control label position with <code>labelPosition</code>: 'top' (default), 'start', 'end', or 'bottom'.
          <strong>Note:</strong> 'bottom' is not recommended as the dropdown will cover the label when opened.
        </p>
      </div>
      <div className="mbe4" style={{ maxWidth: "600px" }}>
        <div className="mbe3">
          <ReactCombobox
            options={stateOptions}
            label="Top Label (Default)"
            labelPosition="top"
            name="pos-top"
            placeholder="Choose a state..."
          />
        </div>

        <div className="mbe3">
          <ReactCombobox
            options={stateOptions}
            label="Start Position"
            labelPosition="start"
            name="pos-start"
            placeholder="Choose a state..."
          />
        </div>

        <div className="mbe3">
          <ReactCombobox
            options={stateOptions}
            label="End Position"
            labelPosition="end"
            name="pos-end"
            placeholder="Choose a state..."
          />
        </div>

        <div className="mbe3">
          <ReactCombobox
            options={stateOptions}
            label="Bottom Position (Not Recommended)"
            labelPosition="bottom"
            name="pos-bottom"
            helpText="The dropdown will cover this label when opened"
            placeholder="Choose a state..."
          />
        </div>
      </div>
    </section>
  );
}
Open in StackBlitz

Usage

TIP

The framework examples below import AgnosticUI as an npm package. Alternatively, you can use the CLI for complete control, AI/LLM visibility, and full code ownership:

bash
npx ag init --framework FRAMEWORK # react, vue, lit, svelte, etc.
npx ag add Combobox

The CLI copies source code directly into your project, giving you full visibility and control. After running npx ag add, you'll receive exact import instructions.

Vue
vue
<template>
  <section>
    <VueCombobox
      v-model:value="selectedState"
      :options="stateOptions"
      label="Select State"
      placeholder="Choose a state..."
      @change="handleChange"
    />

    <VueCombobox
      v-model:value="state"
      :options="stateOptions"
      label="State of Residence"
      placeholder="Select your state..."
      help-text="Choose the state where you currently reside"
    />

    <VueCombobox
      v-model:value="requiredState"
      :options="stateOptions"
      label="Required State"
      :required="true"
      :invalid="isInvalid"
      error-message="State is required"
    />

    <VueCombobox
      v-model:value="clearableState"
      :options="stateOptions"
      label="Select State"
      :clearable="true"
      placeholder="Select and clear..."
    />

    <VueCombobox
      v-model:value="searchState"
      :options="stateOptions"
      label="Search States"
      filter-mode="contains"
      placeholder="Type to search..."
      @search="handleSearch"
    />

    <VueCombobox
      v-model:value="small"
      :options="stateOptions"
      label="Small Combobox"
      size="small"
    />

    <VueCombobox
      v-model:value="large"
      :options="stateOptions"
      label="Large Combobox"
      size="large"
    />

    <VueCombobox
      v-model:value="value"
      :options="optionsWithDisabled"
      label="Select State"
      placeholder="Some options are disabled..."
    />

    <VueCombobox
      v-model:value="asyncValue"
      :options="asyncOptions"
      label="Select State"
      :loading="isLoading"
      loading-text="Fetching states..."
    />

    <VueCombobox
      v-model:value="disabled"
      :options="stateOptions"
      label="Disabled Combobox"
      :disabled="true"
    />
  </section>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import VueCombobox from 'agnosticui-core/combobox/vue';
import type { ComboboxOption } from 'agnosticui-core/combobox';

const stateOptions: ComboboxOption[] = [
  { value: 'ca', label: 'California' },
  { value: 'co', label: 'Colorado' },
  { value: 'ct', label: 'Connecticut' },
  { value: 'fl', label: 'Florida' },
  { value: 'ny', label: 'New York' },
];

const optionsWithDisabled: ComboboxOption[] = [
  { value: 'ca', label: 'California' },
  { value: 'co', label: 'Colorado', disabled: true },
  { value: 'ct', label: 'Connecticut' },
];

const selectedState = ref('');
const state = ref('');
const requiredState = ref('');
const isInvalid = ref(false);
const clearableState = ref('ca');
const searchState = ref('');
const small = ref('');
const large = ref('');
const value = ref('');
const asyncValue = ref('');
const asyncOptions = ref<ComboboxOption[]>([]);
const isLoading = ref(false);
const disabled = ref('ca');

const handleChange = (event: CustomEvent) => {
  console.log('Selected:', event.detail.value);
};

const handleSearch = (event: CustomEvent) => {
  console.log('Search term:', event.detail.searchTerm);
};
</script>
React
tsx
import { useState } from 'react';
import { ReactCombobox } from 'agnosticui-core/combobox/react';
import type { ComboboxOption, ComboboxChangeEvent } from 'agnosticui-core/combobox/react';

export default function ComboboxExample() {
  const stateOptions: ComboboxOption[] = [
    { value: 'ca', label: 'California' },
    { value: 'co', label: 'Colorado' },
    { value: 'ct', label: 'Connecticut' },
    { value: 'fl', label: 'Florida' },
    { value: 'ny', label: 'New York' },
  ];

  const optionsWithDisabled: ComboboxOption[] = [
    { value: 'ca', label: 'California' },
    { value: 'co', label: 'Colorado', disabled: true },
    { value: 'ct', label: 'Connecticut' },
  ];

  const [selectedState, setSelectedState] = useState('');
  const [state, setState] = useState('');
  const [requiredState, setRequiredState] = useState('');
  const [isInvalid, setIsInvalid] = useState(false);
  const [clearableState, setClearableState] = useState('ca');
  const [searchState, setSearchState] = useState('');
  const [small, setSmall] = useState('');
  const [large, setLarge] = useState('');
  const [value, setValue] = useState('');
  const [asyncValue, setAsyncValue] = useState('');
  const [asyncOptions, setAsyncOptions] = useState<ComboboxOption[]>([]);
  const [isLoading, setIsLoading] = useState(false);

  const handleChange = (event: ComboboxChangeEvent) => {
    console.log('Selected:', event.detail.value);
    setSelectedState(event.detail.value);
  };

  const handleSearch = (event: CustomEvent) => {
    console.log('Search term:', event.detail.searchTerm);
  };

  return (
    <section>
      <ReactCombobox
        value={selectedState}
        options={stateOptions}
        label="Select State"
        placeholder="Choose a state..."
        onChange={handleChange}
      />

      <ReactCombobox
        value={state}
        options={stateOptions}
        label="State of Residence"
        placeholder="Select your state..."
        helpText="Choose the state where you currently reside"
        onChange={(e) => setState(e.detail.value)}
      />

      <ReactCombobox
        value={requiredState}
        options={stateOptions}
        label="Required State"
        required
        invalid={isInvalid}
        errorMessage="State is required"
        onChange={(e) => setRequiredState(e.detail.value)}
      />

      <ReactCombobox
        value={clearableState}
        options={stateOptions}
        label="Select State"
        clearable
        placeholder="Select and clear..."
        onChange={(e) => setClearableState(e.detail.value)}
      />

      <ReactCombobox
        value={searchState}
        options={stateOptions}
        label="Search States"
        filterMode="contains"
        placeholder="Type to search..."
        onChange={(e) => setSearchState(e.detail.value)}
        onSearch={handleSearch}
      />

      <ReactCombobox
        value={small}
        options={stateOptions}
        label="Small Combobox"
        size="small"
        onChange={(e) => setSmall(e.detail.value)}
      />

      <ReactCombobox
        value={large}
        options={stateOptions}
        label="Large Combobox"
        size="large"
        onChange={(e) => setLarge(e.detail.value)}
      />

      <ReactCombobox
        value={value}
        options={optionsWithDisabled}
        label="Select State"
        placeholder="Some options are disabled..."
        onChange={(e) => setValue(e.detail.value)}
      />

      <ReactCombobox
        value={asyncValue}
        options={asyncOptions}
        label="Select State"
        loading={isLoading}
        loadingText="Fetching states..."
        onChange={(e) => setAsyncValue(e.detail.value)}
      />

      <ReactCombobox
        value="ca"
        options={stateOptions}
        label="Disabled Combobox"
        disabled
      />
    </section>
  );
}
Lit
ts
import { LitElement, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import 'agnosticui-core/combobox';
import type { ComboboxOption, ComboboxChangeEvent } from 'agnosticui-core/combobox';

@customElement('combobox-example')
export class ComboboxExample extends LitElement {
  @state()
  private stateOptions: ComboboxOption[] = [
    { value: 'ca', label: 'California' },
    { value: 'co', label: 'Colorado' },
    { value: 'ct', label: 'Connecticut' },
    { value: 'fl', label: 'Florida' },
    { value: 'ny', label: 'New York' },
  ];

  @state()
  private selectedState = '';

  @state()
  private clearableState = 'ca';

  @state()
  private isLoading = false;

  private handleChange(event: ComboboxChangeEvent) {
    console.log('Selected:', event.detail.value);
    this.selectedState = event.detail.value;
  }

  private handleSearch(event: CustomEvent) {
    console.log('Search term:', event.detail.searchTerm);
  }

  render() {
    return html`
      <ag-combobox
        .options=${this.stateOptions}
        .value=${this.selectedState}
        label="Select State"
        placeholder="Choose a state..."
        @change=${this.handleChange}
      ></ag-combobox>

      <ag-combobox
        .options=${this.stateOptions}
        label="State of Residence"
        placeholder="Select your state..."
        help-text="Choose the state where you currently reside"
      ></ag-combobox>

      <ag-combobox
        .options=${this.stateOptions}
        label="Required State"
        ?required=${true}
        ?invalid=${false}
        error-message="State is required"
      ></ag-combobox>

      <ag-combobox
        .options=${this.stateOptions}
        .value=${this.clearableState}
        label="Select State"
        ?clearable=${true}
        placeholder="Select and clear..."
      ></ag-combobox>

      <ag-combobox
        .options=${this.stateOptions}
        label="Search States"
        filter-mode="contains"
        placeholder="Type to search..."
        @search=${this.handleSearch}
      ></ag-combobox>

      <ag-combobox
        .options=${this.stateOptions}
        label="Small Combobox"
        size="small"
      ></ag-combobox>

      <ag-combobox
        .options=${this.stateOptions}
        label="Large Combobox"
        size="large"
      ></ag-combobox>

      <ag-combobox
        .options=${[]}
        label="Select State"
        ?loading=${this.isLoading}
        loading-text="Fetching states..."
      ></ag-combobox>

      <ag-combobox
        .options=${this.stateOptions}
        .value=${'ca'}
        label="Disabled Combobox"
        ?disabled=${true}
      ></ag-combobox>
    `;
  }
}

API

Props

PropTypeDefaultDescription
optionsComboboxOption[][]Array of options to display in the listbox
valuestring''Currently selected value
defaultValuestring''Initial value (uncontrolled mode)
placeholderstring''Placeholder text for the input
labelstring''Label text for the combobox
labelHiddenbooleanfalseVisually hide the label (still accessible to screen readers)
noLabelbooleanfalseRemove the label entirely
labelPosition'top' | 'start' | 'end' | 'bottom''top'Position of the label relative to the combobox
ariaLabelstring | nullnullARIA label for accessibility
helpTextstring''Help text displayed below the input
errorMessagestring''Error message displayed below the input
idstring(auto-generated)Unique ID for the combobox
autocomplete'list' | 'none''list'Autocomplete behavior
filterMode'startsWith' | 'contains' | 'none''startsWith'How to filter options based on input
clearablebooleanfalseShow clear button when value is selected
disabledbooleanfalseDisable the combobox
readonlybooleanfalseMake the combobox read-only
requiredbooleanfalseMark the combobox as required
invalidbooleanfalseMark the combobox as invalid
size'small' | 'default' | 'large''default'Size variant
maxVisibleOptionsnumberundefinedMaximum number of visible options
closeOnSelectbooleantrueClose listbox after selecting an option
loadingbooleanfalseShow loading state
loadingTextstring'Loading...'Loading message text
noResultsTextstring'No results found'No results message text

ComboboxOption Interface

typescript
interface ComboboxOption {
  value: string;
  label: string;
  disabled?: boolean;
  group?: string;
  icon?: string;
  description?: string;
  metadata?: Record<string, unknown>;
}

Events

EventDetailDescription
change{ value: string; option: ComboboxOption | null }Fired when selection changes
select{ option: ComboboxOption; value: string }Fired when an option is selected
search{ searchTerm: string }Fired when search term changes
open{ open: boolean }Fired when listbox opens
close{ open: boolean }Fired when listbox closes
focusFocusEventFired when input gains focus
blurFocusEventFired when input loses focus

Methods

MethodSignatureDescription
focus()() => voidFocus the input
blur()() => voidBlur the input
open()() => voidOpen the listbox
close()() => voidClose the listbox
toggle()() => voidToggle listbox open/closed
selectOption()(optionOrValue: ComboboxOption | string) => voidSelect an option by value or object
clearSelection()() => voidClear the current selection

CSS Parts

Use ::part() to style component internals:

css
ag-combobox::part(ag-combobox-wrapper) { }
ag-combobox::part(ag-combobox-label) { }
ag-combobox::part(ag-combobox-input-wrapper) { }
ag-combobox::part(ag-combobox-input) { }
ag-combobox::part(ag-combobox-toggle-button) { }
ag-combobox::part(ag-combobox-clear-button) { }
ag-combobox::part(ag-combobox-listbox) { }
ag-combobox::part(ag-combobox-option) { }
ag-combobox::part(ag-combobox-loading) { }
ag-combobox::part(ag-combobox-no-results) { }
ag-combobox::part(ag-combobox-help-text) { }
ag-combobox::part(ag-combobox-error-message) { }

Design Tokens

Customize appearance with CSS custom properties:

css
ag-combobox {
  --combobox-min-width: 200px;
  --combobox-max-width: 100%;
  --combobox-listbox-max-height: 300px;
  --combobox-option-padding: var(--ag-space-3) var(--ag-space-4);
  --combobox-option-selected-bg: var(--ag-blue-100);
  --combobox-option-hover-bg: var(--ag-background-tertiary);
  --combobox-option-focus-bg: var(--ag-blue-50);
  --combobox-toggle-size: var(--ag-space-6);
}

Keyboard Navigation

KeyAction
ArrowDownOpens listbox (if closed) and moves focus to next option
ArrowUpOpens listbox (if closed) and moves focus to previous option
EnterSelects the focused option and closes listbox
EscapeCloses listbox (if open) or clears input (if closed with text)
TabCloses listbox and moves focus to next element
HomeMoves cursor to start of input
EndMoves cursor to end of input
Printable charactersFilters options based on typed text

Accessibility

The Combobox component follows WCAG 2.1 Level AA guidelines and implements the W3C ARIA APG Combobox Pattern:

  • ARIA attributes: Proper role, aria-expanded, aria-controls, aria-activedescendant, aria-autocomplete
  • Keyboard navigation: Full keyboard support with arrow keys, Enter, Escape, Tab
  • Focus management: Uses aria-activedescendant pattern for predictable screen reader behavior
  • Screen reader announcements: Live regions announce options as user navigates
  • Focus indicators: Clear 2px outline with 3:1 contrast ratio
  • High contrast mode: Tested with Windows High Contrast Mode
  • Reduced motion: Respects prefers-reduced-motion setting
  • Color independence: Selection state indicated with visual markers beyond color

Screen Reader Support

Tested with:

  • NVDA (Windows)
  • JAWS (Windows)
  • VoiceOver (macOS, iOS)
  • TalkBack (Android)

Filter Modes

startsWith (default)

Matches options that start with the search term. Fast and predictable.

vue
<VueCombobox
  :options="options"
  filter-mode="startsWith"
/>

contains

Matches options that contain the search term anywhere. More forgiving for users.

vue
<VueCombobox
  :options="options"
  filter-mode="contains"
/>

none

Disables client-side filtering. Useful for server-side filtering scenarios.

vue
<VueCombobox
  :options="options"
  filter-mode="none"
  @search="handleServerSearch"
/>

Best Practices

When to Use

Use Combobox when:

  • Users need to search through many options (>10)
  • You want to provide typeahead/autocomplete functionality
  • Users might know the exact value they're looking for
  • You need accessible keyboard navigation
  • You want to allow filtering/searching

Don't use Combobox when:

  • You have fewer than 5 simple options (use Radio or Select)
  • Users need to compare options visually (use Radio group)
  • The selection is critical and requires user confirmation (use Select with explicit submit)

Accessibility Tips

  1. Always provide a label: Use label prop or aria-label for screen readers
  2. Use help text: Provide guidance with helpText prop
  3. Clear error messages: Use errorMessage with specific, actionable messages
  4. Test keyboard navigation: Ensure all features work without a mouse
  5. Test with screen readers: Verify announcements are clear and helpful

Performance

  • Options are filtered in real-time as users type
  • Uses efficient startsWith or contains algorithms
  • Limits visible options with maxVisibleOptions for large datasets
  • For very large datasets (>1000), consider server-side filtering with filter-mode="none"

Phase 2 Features (Future)

The following features are planned for Phase 2:

  • Multiselect: Select multiple options with tags
  • Grouped options: Organize options into categories
  • Rich content: Icons, descriptions, avatars in options
  • Async filtering: Built-in debounce and async support
  • Fuzzy matching: More forgiving search algorithm
  • Virtual scrolling: Handle thousands of options efficiently
  • Mobile optimization: Fullscreen mode on small screens