Skip to content

Listbox

A control that allows the user to toggle between checked and not checked.
Fruits
Apple
Banana
Blueberry
Grapes
Pineapple
Vegetables
Aubergine
Broccoli
Carrot
Courgette
Leek
vue
<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.

sh
$ npm add radix-vue

Anatomy

Import all parts and piece them together.

vue
<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.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.
If omitted, inherits globally from DirectionProvider or assumes LTR (left-to-right) reading mode.

disabled
boolean

When true, prevents the user from interacting with listbox

highlightOnHover
boolean

When true, hover over item will trigger highlight

modelValue
AcceptableValue | AcceptableValue[]

The controlled value of the listbox. Can be binded-with with v-model.

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.
Mainly so arrow navigation is done accordingly (left & right vs. up & down)

selectionBehavior
'toggle'
'toggle' | 'replace'

How multiple selection should behave in the collection.

EmitPayload
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 AttributeValue
[data-disabled]Present when disabled

Filter

Input element to perform filtering.

PropDefaultType
as
'input'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

EmitPayload
update:modelValue
[string]

Event handler called when the value changes.

Slots (default)Payload
modelValue
string | undefined

Current input values

Data AttributeValue
[data-disabled]Present when disabled

Content

Contains all the listbox group and items.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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 true, prevents the user from interacting with the item.

value*
AcceptableValue

The value given as data when submitted with a name.

EmitPayload
select
[event: SelectEvent<AcceptableValue>]

Event handler called when the selecting item.
It can be prevented by calling event.preventDefault.

Data AttributeValue
[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.

PropDefaultType
as
'span'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

PropDefaultType
as
'div'
AsTag | Component

The element or component this component should render as. Can be overwrite by asChild

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.

PropDefaultType
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>

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.

vue
<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.

vue
<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

vue
<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.

vue
<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

KeyDescription
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.