Listbox
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ListboxContent, ListboxGroup, ListboxGroupLabel, ListboxItem, ListboxItemIndicator, ListboxRoot } from 'radix-vue'
const fruits = ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple']
const vegetables = ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
</script>
<template>
<ListboxRoot class=" flex flex-col rounded-lg border bg-white text-green9 mx-auto ">
<ListboxContent class="p-[5px] w-48 h-72 overflow-auto">
<ListboxGroup>
<ListboxGroupLabel class="px-[25px] text-xs leading-[25px] text-mauve11">
Fruits
</ListboxGroupLabel>
<ListboxItem
v-for="i in fruits"
:key="i"
:value="i"
class="w-full flex items-center px-[25px] h-[25px] leading-none text-[13px] relative text-green9 select-none outline-none data-[highlighted]:ring-green9 data-[highlighted]:ring-1 focus:ring-green9 focus:ring-1 data-[state=checked]:bg-green9 data-[state=checked]:text-white data-[disabled]:opacity-50 rounded"
>
<ListboxItemIndicator
class="absolute left-0 w-[25px] inline-flex items-center justify-center"
>
<Icon icon="radix-icons:check" />
</ListboxItemIndicator>
<span>{{ i }}</span>
</ListboxItem>
</ListboxGroup>
<ListboxGroup class="mt-2">
<ListboxGroupLabel class="px-[25px] text-xs leading-[25px] text-mauve11">
Vegetables
</ListboxGroupLabel>
<ListboxItem
v-for="i in vegetables"
:key="i"
:value="i"
class="w-full flex items-center px-[25px] h-[25px] leading-none text-[13px] relative text-green9 select-none outline-none data-[highlighted]:ring-green9 data-[highlighted]:ring-1 focus:ring-green9 focus:ring-1 data-[state=checked]:bg-green9 data-[state=checked]:text-white data-[disabled]:opacity-50 rounded"
>
<ListboxItemIndicator
class="absolute left-0 w-[25px] inline-flex items-center justify-center"
>
<Icon icon="radix-icons:check" />
</ListboxItemIndicator>
<span>{{ i }}</span>
</ListboxItem>
</ListboxGroup>
</ListboxContent>
</ListboxRoot>
</template>
Features
- Can be controlled or uncontrolled.
- Supports items, labels, groups of items.
- Focus is fully managed.
- Full keyboard navigation.
- Supports Right to Left direction.
- Different selection behavior.
Installation
Install the component from your command line.
$ npm add radix-vue
Anatomy
Import all parts and piece them together.
<script setup>
import { ListboxContent, ListboxFilter, ListboxGroup, ListboxGroupLabel, ListboxItem, ListboxItemIndicator, ListboxRoot, ListboxVirtualizer } from 'radix-vue'
</script>
<template>
<ListboxRoot>
<ListboxFilter />
<ListboxContent>
<ListboxItem>
<ListboxItemIndicator />
</ListboxItem>
<!-- or with group -->
<ListboxGroup>
<ListboxGroupLabel />
<ListboxItem>
<ListboxItemIndicator />
</ListboxItem>
</ListboxGroup>
<!-- or with virtual -->
<ListboxVirtualizer>
<ListboxItem>
<ListboxItemIndicator />
</ListboxItem>
</ListboxVirtualizer>
</ListboxContent>
</ListboxRoot>
</template>
API Reference
Root
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' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change 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 | boolean When | |
highlightOnHover | boolean When | |
modelValue | AcceptableValue | AcceptableValue[] The controlled value of the listbox. Can be binded with with | |
multiple | boolean Whether multiple options can be selected or not. | |
name | string The name of the listbox. Submitted with its owning form as part of a name/value pair. | |
orientation | 'vertical' | 'vertical' | 'horizontal' The orientation of the listbox. |
selectionBehavior | 'toggle' | 'toggle' | 'replace' How multiple selection should behave in the collection. |
Emit | Payload |
---|---|
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. |
Slots (default) | Payload |
---|---|
modelValue | AcceptableValue | AcceptableValue[] | undefined Current active value |
Data Attribute | Value |
---|---|
[data-disabled] | Present when disabled |
Filter
Input element to perform filtering.
Prop | Default | Type |
---|---|---|
as | 'input' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
autoFocus | boolean Focus on element when mounted. | |
modelValue | string The controlled value of the filter. Can be binded with with v-model. |
Emit | Payload |
---|---|
update:modelValue | [string] Event handler called when the value changes. |
Slots (default) | Payload |
---|---|
modelValue | string | undefined Current input values |
Data Attribute | Value |
---|---|
[data-disabled] | Present when disabled |
Content
Contains all the listbox group and items.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Item
The item component.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disabled | boolean When | |
value* | AcceptableValue The value given as data when submitted with a |
Emit | Payload |
---|---|
select | [event: SelectEvent<AcceptableValue>] Event handler called when the selecting item. |
Data Attribute | Value |
---|---|
[data-state] | "checked" | "unchecked" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
ItemIndicator
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' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Group
Used to group multiple items. use in conjunction with ListboxGroupLabel
to ensure good accessibility via automatic labelling.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
GroupLabel
Used to render the label of a group. It won't be focusable using arrow keys.
Prop | Default | Type |
---|---|---|
as | 'div' | AsTag | Component The element or component this component should render as. Can be overwrite by |
asChild | boolean Change 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 |
Virtualizer
Virtual container to achieve list virtualization.
Prop | Default | Type |
---|---|---|
estimateSize | number Estimated size (in px) of each item | |
options* | AcceptableValue[] List of items | |
textContent | ((option: AcceptableValue) => string) text content for each item to achieve type-ahead feature |
Slots (default) | Payload |
---|---|
option | string | number | false | true | Record<string, any> |
virtualizer | Virtualizer<Element | Window, Element> |
virtualItem | VirtualItem<Element> |
Examples
Binding objects as values
Unlike native HTML form controls which only allow you to provide strings as values, radix-vue
supports binding complex objects as well.
<script setup lang="ts">
import { ref } from 'vue'
import { ListboxContent, ListboxFilter, ListboxItem, ListboxRoot } from 'radix-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>
<ListboxRoot v-model="selectedPeople">
<ListboxContent>
<ListboxItem
v-for="person in people"
:key="person.id"
:value="person"
:disabled="person.unavailable"
>
{{ person.name }}
</ListboxItem>
</ListboxContent>
</ListboxRoot>
</template>
Selecting multiple values
The Listbox
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 { ref } from 'vue'
import { ListboxRoot } from 'radix-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>
<ListboxRoot
v-model="selectedPeople"
multiple
>
...
</ListboxRoot>
</template>
Custom filtering
<script setup lang="ts">
import { ref } from 'vue'
import { ListboxContent, ListboxFilter, ListboxItem, ListboxRoot } from 'radix-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 filteredPeople = computed(() =>
searchTerm.value === ''
? people
: people.filter((person) => {
return person.name.toLowerCase().includes(searchTerm.value.toLowerCase())
})
)
</script>
<template>
<ListboxRoot
v-model="selectedPeople"
>
<ListboxFilter v-model="searchTerm" />
<ListboxContent>
<ListboxItem
v-for="person in filteredPeople"
:key="person.id"
:value="person"
>
{{ person.name }}
</ListboxItem>
</ListboxContent>
</ListboxRoot>
</template>
Virtual List
Rendering a long list of item can slow down the app, thus using virtualization would significantly improve the performance.
<script setup lang="ts">
import { ref } from 'vue'
import { ListboxContent, ListboxFilter, ListboxItem, ListboxRoot, ListboxVirtualizer } from 'radix-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>
<ListboxRoot>
<ListboxContent>
<!-- checkout https://radix-vue.com/components/listbox.html#virtualizer -->
<ListboxVirtualizer
v-slot="{ option }"
:options="people"
:text-content="(opt) => opt.name"
>
<ListboxItem :value="option">
{{ person.name }}
</ListboxItem>
</ListboxVirtualizer>
</ListboxContent>
</ListboxRoot>
</template>
Accessibility
Adheres to the Listbox WAI-ARIA design pattern.
Keyboard Interactions
Key | Description |
---|---|
Enter | When highlight on ListboxItem , selects the focused item. |
ArrowDown | When focus is on ListboxItem , moves focus to the next item. |
ArrowUp | When focus is on ListboxItem , moves focus to the previous item. |
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. |