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
<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
<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
Property | Required | Description | Type | Default |
---|---|---|---|---|
modelValue | true | The selected value. Set to an array to enable multiselection. | any | any[] | undefined |
displayValue | false | A function that takes the combobox value and returns a string representation that is shown in the input. | (value: unknown | unknown[]) => string | Calls .toString() on the provided value. For multi-selectable combobox converts every value to string and joins the results with a comma. |
name | false | The name of the field that is submitted when using inside a form. | string | undefined |
formValue | false | A 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> | string | Returns the value unchanged |
id | false | The ID of the combobox input. | string | Auto-generated |
activatorId | false | The ID of the activator element. | string | Equals 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:
Property | Description | Type |
---|---|---|
attrs | Required attributes that must be binded to the activator. | object |
isOpen | The status of the popover. | Ref<boolean> |
toggle | Toggle the popover. | () => void |
displayValue | The current display value. | ComputedRef<string> |
popoverId | The 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
Property | Required | Description | Type | Default |
---|---|---|---|---|
value | true | Value assigned to the item. | any | |
disabled | false | Whether the item is disabled. | boolean | false |
id | false | The ID of the item. | string | Auto-generated |
Slots
default
Attribute | Description | Type |
---|---|---|
isSelected | Whether the item is selected. | boolean |
isActive | Whether 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:
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:
const value = {
login: "johny1223",
email: "johny@gmail.com",
isAdmin: false,
};
const formValue = (value) => value.login;
// Will be submitted as
// name=johny@gmail.com