Skip to content

Combobox Components

A combobox is a composite widget that combines an input field with a popup providing possible values for that input field.

Based on:

Demo

Autocomplete Combobox
Select-Only Combobox
Multiselectable Combobox
Code
vue
<template>
  <div class="d-demo">
    <div class="d-demo__column">
      <h5>Autocomplete Combobox</h5>
      <Combobox v-model="fruit">
        <ComboboxInput
          @input="query = $event.target.value"
          @change="query = ''"
          placeholder="Choose a fruit..."
          class="d-input"
        />
        <ComboboxOptions class="d-menu">
          <div class="d-no-options" v-if="filteredFruits.length == 0">
            Nothing found for <strong>"{{ query }}"</strong>
          </div>
          <ComboboxOption
            v-for="opt in filteredFruits"
            :key="opt"
            :value="opt"
            v-slot="{ isSelected }"
            class="d-menu-item"
          >
            {{ opt }}
            <svg
              v-if="isSelected"
              xmlns="http://www.w3.org/2000/svg"
              width="1rem"
              height="1rem"
              viewBox="0 0 256 256"
            >
              <path
                fill="currentColor"
                d="m232.49 80.49l-128 128a12 12 0 0 1-17 0l-56-56a12 12 0 1 1 17-17L96 183L215.51 63.51a12 12 0 0 1 17 17Z"
              />
            </svg>
          </ComboboxOption>
        </ComboboxOptions>
      </Combobox>
    </div>

    <div class="d-demo__column">
      <h5>Select-Only Combobox</h5>
      <Combobox v-model="fruit" v-slot="{ attrs, displayValue }">
        <button class="d-input d-input--select" v-bind="attrs">
          {{ displayValue || "Select a fruit..." }}
        </button>
        <ComboboxOptions class="d-menu">
          <ComboboxOption
            v-for="opt in fruits"
            :value="opt"
            v-slot="{ isSelected }"
            class="d-menu-item"
          >
            {{ opt }}
            <svg
              v-if="isSelected"
              xmlns="http://www.w3.org/2000/svg"
              width="1rem"
              height="1rem"
              viewBox="0 0 256 256"
            >
              <path
                fill="currentColor"
                d="m232.49 80.49l-128 128a12 12 0 0 1-17 0l-56-56a12 12 0 1 1 17-17L96 183L215.51 63.51a12 12 0 0 1 17 17Z"
              />
            </svg>
          </ComboboxOption>
        </ComboboxOptions>
      </Combobox>
    </div>

    <div class="d-demo__column">
      <h5>Multiselectable Combobox</h5>
      <Combobox
        v-model="value"
        :displayValue="(items: any[]) => items.map((i) => i.name).join(', ')"
        v-slot="{ attrs, displayValue }"
      >
        <button class="d-input d-input--select" v-bind="attrs">
          {{ displayValue || "Select some people..." }}
        </button>
        <ComboboxOptions class="d-menu">
          <ComboboxOption
            v-for="opt in options"
            :value="opt"
            v-slot="{ isSelected }"
            class="d-menu-item"
          >
            {{ opt.name }}
            <svg
              v-if="isSelected"
              xmlns="http://www.w3.org/2000/svg"
              width="1rem"
              height="1rem"
              viewBox="0 0 256 256"
            >
              <path
                fill="currentColor"
                d="m232.49 80.49l-128 128a12 12 0 0 1-17 0l-56-56a12 12 0 1 1 17-17L96 183L215.51 63.51a12 12 0 0 1 17 17Z"
              />
            </svg>
          </ComboboxOption>
        </ComboboxOptions>
      </Combobox>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from "vue";
import {
  Combobox,
  ComboboxInput,
  ComboboxOptions,
  ComboboxOption,
} from "../../../src/main";

const fruit = ref("");
const fruits = ["Apple", "Banana", "Grape", "Orange"];

const query = ref("");
const filteredFruits = computed(() =>
  query.value === ""
    ? fruits
    : fruits.filter((fruit) =>
        fruit
          .toLowerCase()
          .replace(/\s+/g, "")
          .includes(query.value.toLowerCase().replace(/\s+/g, ""))
      )
);

const value = ref([]);
const options = [
  {
    id: 0,
    name: "John Doe",
  },
  {
    id: 1,
    name: "Cris Doe",
  },
  {
    id: 2,
    name: "Michael Doe",
  },
  {
    id: 3,
    name: "Kim Doe",
  },
  {
    id: 4,
    name: "Boris Doe",
  },
  {
    id: 5,
    name: "Angelina Doe",
  },
];
</script>

Complex Widget Example

Autocomplete + tags

Code
vue
<template>
  <div class="d-demo">
    <Combobox
      v-model="value"
      :displayValue="() => ''"
      activatorId="demoId"
      v-slot="{ popoverId, toggle, isOpen }"
    >
      <div id="demoId" class="d-combobox">
        <div class="d-combobox__tags">
          <div v-for="item in value.slice(0, 3)" class="d-tag">
            {{ item.name }}
          </div>
          <div v-if="value.length > 3">+{{ value.length - 3 }}</div>
        </div>

        <ComboboxInput
          @input="query = $event.target.value"
          @focus="query = ''"
          placeholder="Type to search..."
        />

        <button
          @click="toggle"
          :aria-controls="isOpen ? popoverId : undefined"
          :aria-expanded="isOpen"
          aria-haspopup="listbox"
          tabindex="-1"
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="24"
            height="24"
            viewBox="0 0 20 20"
          >
            <path
              fill="currentColor"
              fill-rule="evenodd"
              d="M10 3a.75.75 0 0 1 .55.24l3.25 3.5a.75.75 0 1 1-1.1 1.02L10 4.852L7.3 7.76a.75.75 0 0 1-1.1-1.02l3.25-3.5A.75.75 0 0 1 10 3Zm-3.76 9.2a.75.75 0 0 1 1.06.04l2.7 2.908l2.7-2.908a.75.75 0 1 1 1.1 1.02l-3.25 3.5a.75.75 0 0 1-1.1 0l-3.25-3.5a.75.75 0 0 1 .04-1.06Z"
              clip-rule="evenodd"
            />
          </svg>
        </button>
      </div>

      <ComboboxOptions class="d-menu">
        <div class="d-no-options" v-if="filteredPeople.length == 0">
          Nothing found for <strong>"{{ query }}"</strong>
        </div>
        <ComboboxOption
          v-for="opt in filteredPeople"
          :key="opt.id"
          :value="opt"
          :disabled="opt.disabled"
          v-slot="{ isSelected }"
          class="d-menu-item"
        >
          {{ opt.name }}
          <svg
            v-if="isSelected"
            xmlns="http://www.w3.org/2000/svg"
            width="1rem"
            height="1rem"
            viewBox="0 0 256 256"
          >
            <path
              fill="currentColor"
              d="m232.49 80.49l-128 128a12 12 0 0 1-17 0l-56-56a12 12 0 1 1 17-17L96 183L215.51 63.51a12 12 0 0 1 17 17Z"
            />
          </svg>
        </ComboboxOption>
      </ComboboxOptions>
    </Combobox>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch } from "vue";
import {
  Combobox,
  ComboboxInput,
  ComboboxOptions,
  ComboboxOption,
} from "../../../src/main";

const value = ref([]);
const options = [
  {
    id: 0,
    name: "John Doe",
  },
  {
    id: 1,
    name: "Cris Doe",
  },
  {
    id: 2,
    name: "Michael Doe",
    disabled: true,
  },
  {
    id: 3,
    name: "Kim Doe",
  },
  {
    id: 4,
    name: "Boris Doe",
  },
  {
    id: 5,
    name: "Angelina Doe",
  },
];

const query = ref("");
const filteredPeople = computed(() =>
  query.value === ""
    ? options
    : options.filter((person) =>
        person.name
          .toLowerCase()
          .replace(/\s+/g, "")
          .includes(query.value.toLowerCase().replace(/\s+/g, ""))
      )
);

watch(
  value,
  () => {
    query.value = "";
  },
  { deep: true }
);
</script>

Combobox

The root component.

Properties

PropertyRequiredDescriptionTypeDefault
modelValuetrueThe selected value. Set to an array to enable multiselection.any | any[]undefined
displayValuefalseA function that takes the combobox value and returns a string representation that is shown in the input.(value: unknown | unknown[]) => stringCalls .toString() on the provided value. For multi-selectable combobox converts every value to string and joins the results with a comma.
namefalseThe name of the field that is submitted when using inside a form.stringundefined
formValuefalseA function that takes a selected item value and transforms it into a value to submit when using inside a form.(item: unknown) => Record<string, string> | stringReturns the value unchanged
idfalseThe ID of the combobox input.stringAuto-generated
activatorIdfalseThe ID of the activator element.stringEquals to the id

Slots

default

The default slot is used to provide a ComboboxInput, possibly an additional activator and a list of options in the ComboboxOptions component. The slot passes the following properties:

PropertyDescriptionType
attrsRequired attributes that must be binded to the activator.object
isOpenThe status of the popover.Ref<boolean>
toggleToggle the popover.() => void
displayValueThe current display value.ComputedRef<string>
popoverIdThe ID of the popover dialog.string

Keyboard interactions

When closed:

  • Space / Enter - Opens the listbox.
  • Down Arrow / Home - Opens the listbox and moves focus to the first item.
  • Up Arrow / End - Opens the listbox and moves focus to the last item.

When open:

  • Space - Selects the currently active item and closes the combobox (if it is not multiselectable).
  • Enter - Selects the currently active item and closes the combobox.
  • Tab - Selects the currently active item, closes the combobox and proceeds to the default tab action.
  • Down Arrow - Moves focus to the next item.
  • Up Arrow - Moves focus to the previous item.
  • Home - Moves focus to the first item.
  • End - Moves focus to the last item.
  • Escape - Closes the listbox.

ComboboxInput

An input field with events and triggers attached to it to work inside a combobox. Has no properties.

ComboboxOptions

Wraps a collection of options, has no properties.

ARIA roles and attributes

Options container has the listbox role.

ComboboxOption

Properties

PropertyRequiredDescriptionTypeDefault
valuetrueValue assigned to the item.any
disabledfalseWhether the item is disabled.booleanfalse
idfalseThe ID of the item.stringAuto-generated

Slots

default

AttributeDescriptionType
isSelectedWhether the item is selected.boolean
isActiveWhether the item is active (focused).boolean

ARIA roles and attributes

Options have the option role.

The following attributes are automatically managed:

Using in a Form

When using Combobox in a form set the name attribute on the root component.

If multiple items are selected, the values will be submitted as an array:

name[0]=value1&name[1]=value2...&name[n]=valueN

If a value is an object each of its properties will be submitted as a separate field:

js
const value = {
  login: "johny1223",
  email: "johny@gmail.com",
  isAdmin: false,
};

// Will be submitted as
// name[login]=johny1223&name[email]=johny@gmail.com&name[isAdmin]=false

Sometimes you may want to transform a value before submitting the form. Use the formValue attribute to provide a transformer function, if multiple items are selected the function will be call for each value:

js
const value = {
  login: "johny1223",
  email: "johny@gmail.com",
  isAdmin: false,
};
const formValue = (value) => value.login;

// Will be submitted as
// name=johny@gmail.com