<script setup lang="ts">
import { AListboxContent, AListboxGroup, AListboxGroupLabel, AListboxItem, AListboxItemIndicator, AListboxRoot } from 'akar';
const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple'];
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek'];
</script>
<template>
<AListboxRoot class="inline-flex items-center relative">
<AListboxContent class="rounded-md bg-background flex flex-col w-48 pointer-events-auto ring ring-ring shadow-lg">
<AListboxGroup class="p-1 isolate">
<AListboxGroupLabel class="text-xs color-text-highlighted font-semibold p-1.5 gap-1.5">
Fruits
</AListboxGroupLabel>
<AListboxItem
v-for="i in fruits"
:key="i"
:value="i"
class="text-sm color-text p-1.5 outline-none flex gap-1.5 w-full cursor-pointer select-none transition-colors-280 items-start relative before:(rounded-md content-empty transition-colors-280 inset-px absolute -z-1) hover:not-[[data-disabled]]:color-text-highlighted hover:not-[[data-disabled]]:before:bg-background-elevated/50"
>
<span class="flex flex-1 flex-col min-w-0">
{{ i }}
</span>
<span class="ms-auto inline-flex gap-1.5 items-center">
<AListboxItemIndicator
as-child
>
<i class="i-lucide:check shrink-0 size-5" />
</AListboxItemIndicator>
</span>
</AListboxItem>
</AListboxGroup>
<AListboxGroup class="mt-2">
<AListboxGroupLabel class="text-xs color-text-highlighted font-semibold p-1.5 gap-1.5">
Vegetables
</AListboxGroupLabel>
<AListboxItem
v-for="i in vegetables"
:key="i"
:value="i"
class="text-sm color-text p-1.5 outline-none flex gap-1.5 w-full cursor-pointer select-none transition-colors-280 items-start relative before:(rounded-md content-empty transition-colors-280 inset-px absolute -z-1) hover:not-[[data-disabled]]:color-text-highlighted hover:not-[[data-disabled]]:before:bg-background-elevated/50"
>
<span class="flex flex-1 flex-col min-w-0">
{{ i }}
</span>
<span class="ms-auto inline-flex gap-1.5 items-center">
<AListboxItemIndicator
as-child
>
<i class="i-lucide:check shrink-0 size-5" />
</AListboxItemIndicator>
</span>
</AListboxItem>
</AListboxGroup>
</AListboxContent>
</AListboxRoot>
</template>
Import all parts and piece them together.
<script setup>
import { AListboxContent, AListboxFilter, AListboxGroup, AListboxGroupLabel, AListboxItem, AListboxItemIndicator, AListboxRoot, AListboxVirtualizer } from 'akar';
</script>
<template>
<AListboxRoot>
<AListboxFilter />
<AListboxContent>
<AListboxItem>
<AListboxItemIndicator />
</AListboxItem>
<!-- or with group -->
<AListboxGroup>
<AListboxGroupLabel />
<AListboxItem>
<AListboxItemIndicator />
</AListboxItem>
</AListboxGroup>
<!-- or with virtual -->
<AListboxVirtualizer>
<AListboxItem>
<AListboxItemIndicator />
</AListboxItem>
</AListboxVirtualizer>
</AListboxContent>
</AListboxRoot>
</template>
Contains all the parts of a listbox. An input will also render when used within a form to ensure events propagate correctly.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
by | string | ((a: AcceptableValue, b: AcceptableValue) => boolean)Use this to compare objects by a particular field, or pass your own comparison function for complete control over how objects are compared. | |
defaultValue | AcceptableValue | AcceptableValue[]The value of the listbox when initially rendered. Use when you do not need to control the state of the Listbox | |
dir | 'ltr' | 'rtl'The reading direction of the listbox when applicable. | |
disabled | booleanWhen | |
highlightOnHover | booleanWhen | |
modelValue | AcceptableValue | AcceptableValue[] | |
multiple | booleanWhether multiple options can be selected or not. | |
name | stringThe name of the field. Submitted with its owning form as part of a name/value pair. | |
orientation | 'vertical' | 'horizontal' | 'vertical'The orientation of the listbox. |
required | booleanWhen | |
selectionBehavior | 'toggle' | 'replace' | 'toggle'How multiple selection should behave in the collection. |
| Event | Type |
|---|---|
entryFocus | [event: CustomEvent<any>]Event handler called when container is being focused. Can be prevented. |
highlight | [payload: { ref: HTMLElement; value: AcceptableValue; }]Event handler when highlighted element changes. |
leave | [event: Event]Event handler called when the mouse leave the container |
update:modelValue | [value: AcceptableValue]Event handler called when the value changes. |
| Slot | Type |
|---|---|
modelValue | AcceptableValue | AcceptableValue[] The controlled value of the listbox. Can be binded with with |
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
Input element to perform filtering.
| Prop | Default | Type |
|---|---|---|
as | 'input' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
autoFocus | booleanFocus on element when mounted. | |
disabled | booleanWhen | |
modelValue | stringThe controlled value of the listbox. Can be binded with with |
| Event | Type |
|---|---|
update:modelValue | [string]Event handler called when the value changes. |
| Slot | Type |
|---|---|
modelValue | string The controlled value of the listbox. Can be binded with with |
| Attribute | Value |
|---|---|
[data-disabled] | Present when disabled |
Contains all the listbox group and items.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
The item component.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | booleanWhen | |
value* | AcceptableValueThe value given as data when submitted with a |
| Event | Type |
|---|---|
select | [event: SelectEvent<AcceptableValue>]Event handler called when the selecting item. |
| Attribute | Value |
|---|---|
[data-state] | 'checked' | 'unchecked' |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
Renders when the item is selected. You can style this element directly, or you can use it as a wrapper to put an icon into, or both.
| Prop | Default | Type |
|---|---|---|
as | 'span' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Used to group multiple items. use in conjunction with AListboxGroupLabel to ensure good accessibility via automatic labelling.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Used to render the label of a group. It won't be focusable using arrow keys.
| Prop | Default | Type |
|---|---|---|
as | 'div' | APrimitiveAsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
for | string |
Virtual container to achieve list virtualization.
| Prop | Default | Type |
|---|---|---|
estimateSize | numberEstimated size (in px) of each item | |
options* | AcceptableValue[]List of items | |
overscan | numberNumber of items rendered outside the visible area | |
textContent | ((option: AcceptableValue) => string)Text content for each item to achieve type-ahead feature |
| Slot | Type |
|---|---|
option | null | string | number | bigint | Record<string, any> |
virtualizer | Virtualizer<HTMLElement, Element> |
virtualItem | VirtualItem |
Unlike native HTML form controls which only allow you to provide strings as values, akar supports binding complex objects as well.
<script setup lang="ts">
import { AListboxContent, AListboxItem, AListboxRoot } from 'akar';
import { ref } from 'vue';
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
];
const selectedPeople = ref(people[0]);
</script>
<template>
<AListboxRoot v-model="selectedPeople">
<AListboxContent>
<AListboxItem
v-for="person in people"
:key="person.id"
:value="person"
:disabled="person.unavailable"
>
{{ person.name }}
</AListboxItem>
</AListboxContent>
</AListboxRoot>
</template>
The AListbox component allows you to select multiple values. You can enable this by providing an array of values instead of a single value.
<script setup lang="ts">
import { AListboxRoot } from 'akar';
import { ref } from 'vue';
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
];
const selectedPeople = ref([people[0], people[1]]);
</script>
<template>
<AListboxRoot
v-model="selectedPeople"
multiple
>
...
</AListboxRoot>
</template>
<script setup lang="ts">
import { AListboxContent, AListboxFilter, AListboxItem, AListboxRoot, useFilter } from 'akar';
import { ref } from 'vue';
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
];
const selectedPeople = ref(people[0]);
const searchTerm = ref('');
const { startsWith } = useFilter({ sensitivity: 'base' });
const filteredPeople = computed(() => people.filter((p) => startsWith(p.name, searchTerm.value)));
</script>
<template>
<AListboxRoot v-model="selectedPeople">
<AListboxFilter v-model="searchTerm" />
<AListboxContent>
<AListboxItem
v-for="person in filteredPeople"
:key="person.id"
:value="person"
>
{{ person.name }}
</AListboxItem>
</AListboxContent>
</AListboxRoot>
</template>
Rendering a long list of item can slow down the app, thus using virtualization would significantly improve the performance.
See the virtualization guide for more general info on virtualization.
<script setup lang="ts">
import { AListboxContent, AListboxFilter, AListboxItem, AListboxRoot, AListboxVirtualizer } from 'akar';
import { ref } from 'vue';
const people = [
{ id: 1, name: 'Durward Reynolds' },
{ id: 2, name: 'Kenton Towne' },
{ id: 3, name: 'Therese Wunsch' },
{ id: 4, name: 'Benedict Kessler' },
{ id: 5, name: 'Katelyn Rohan' },
// and a lot more
];
</script>
<template>
<AListboxRoot>
<AListboxContent>
<AListboxVirtualizer
v-slot="{ option }"
:options="people"
:text-content="(opt) => opt.name"
>
<AListboxItem :value="option">
{{ person.name }}
</AListboxItem>
</AListboxVirtualizer>
</AListboxContent>
</AListboxRoot>
</template>
Adheres to the AListbox WAI-ARIA design pattern.
| Key | Description |
|---|---|
Enter | When highlight on |
ArrowDown | When focus is on |
ArrowUp | When focus is on |
Home | Moves focus and highlight to the first item. |
End | Moves focus and highlight to the last item. |
Ctrl/Cmd + A | Select all the items. |