Select
A headless dropdown select component with single and multi-selection support. Uses createSelection for state management, useVirtualFocus for keyboard navigation, and usePopover for native popover positioning.
Usage
The Select component provides a compound pattern for building accessible dropdown selects. It supports v-model for both single values and arrays (multi-select mode).
Selected: None
<script setup lang="ts">
import { Select } from '@vuetify/v0'
import { shallowRef } from 'vue'
const color = shallowRef<string>()
const colors = [
{ id: 'red', label: 'Red' },
{ id: 'orange', label: 'Orange' },
{ id: 'green', label: 'Green' },
{ id: 'blue', label: 'Blue' },
{ id: 'purple', label: 'Purple' },
]
</script>
<template>
<div class="flex flex-col gap-4 max-w-xs mx-auto">
<Select.Root v-model="color">
<Select.Activator class="flex items-center justify-between w-full px-3 py-2 rounded-lg border border-divider bg-surface text-on-surface text-sm cursor-pointer focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-2">
<Select.Value v-slot="{ selectedValue }">
{{ selectedValue }}
</Select.Value>
<Select.Placeholder class="text-on-surface-variant">Choose a color…</Select.Placeholder>
<Select.Cue v-slot="{ isOpen }" class="text-xs opacity-50">
{{ isOpen ? '▴' : '▾' }}
</Select.Cue>
</Select.Activator>
<Select.Content class="p-1 rounded-lg border border-divider bg-surface shadow-lg" :style="{ minWidth: 'anchor-size(width)' }">
<Select.Item
v-for="item in colors"
:id="item.id"
:key="item.id"
:value="item.label"
>
<template #default="{ isSelected, isHighlighted }">
<div
class="px-3 py-2 rounded-md cursor-default select-none text-sm"
:class="[
isHighlighted
? 'bg-primary text-on-primary'
: isSelected
? 'text-primary font-medium'
: 'text-on-surface hover:bg-surface-variant',
]"
>
{{ item.label }}
</div>
</template>
</Select.Item>
</Select.Content>
</Select.Root>
<p class="text-sm text-on-surface-variant">
Selected: {{ color ?? 'None' }}
</p>
</div>
</template>
Anatomy
<script setup lang="ts">
import { Select } from '@vuetify/v0'
</script>
<template>
<Select.Root>
<Select.Activator>
<Select.Value />
<Select.Placeholder />
<Select.Cue />
</Select.Activator>
<Select.Content>
<Select.Item />
</Select.Content>
</Select.Root>
</template>Architecture
The Root creates selection, virtual focus, and popover contexts. The Activator serves as the combobox trigger with keyboard event handling. Content renders via the native popover API with CSS anchor positioning. Each Item registers with the selection context and provides data attributes for styling.
Examples
Disabled States
Both individual items and the entire select can be disabled. Disabled items are skipped by virtual focus keyboard navigation. The disabled prop on Root prevents the dropdown from opening.
Selected: None
Multi-Select
Set multiple on Root to enable multi-selection. The dropdown stays open after each selection. v-model binds to an array of IDs. The Value slot receives selectedValues for rendering chips, tags, or comma-separated text.
Selected: None
Accessibility
The Select implements the WAI-ARIA Combobox↗ pattern with a listbox popup.
ARIA Attributes
| Attribute | Value | Component |
|---|---|---|
role | combobox | Activator |
role | listbox | Content |
role | option | Item |
aria-expanded | true / false | Activator |
aria-haspopup | listbox | Activator |
aria-controls | listbox ID | Activator |
aria-selected | true / false | Item |
aria-disabled | true | Item (when disabled) |
aria-multiselectable | true | Content (when multiple) |
Keyboard Navigation
| Key | Action |
|---|---|
Enter / Space | Open dropdown, or select highlighted item |
ArrowDown | Open dropdown, or move highlight down |
ArrowUp | Open dropdown, or move highlight up |
Home | Move highlight to first item |
End | Move highlight to last item |
Escape | Close dropdown |
Tab | Close dropdown and move focus |
Selection.Root
Props
namespace
stringNamespace for dependency injection (must match SelectionItem namespace)
Default: "v0:selection"
mandatory
boolean | "force"Controls mandatory selection behavior: - false (default): No mandatory selection enforcement - true: Prevents deselecting the last selected item - `force`: Automatically selects the first non-disabled item on registration
Default: false
modelValue
T | T[]Events
update:model-value
[value: T | T[]]Slots
default
SelectionRootSlotPropsSelect.Root
Props
mandatory
boolean | "force"Controls mandatory selection behavior: - false (default): No mandatory selection enforcement - true: Prevents deselecting the last selected item - `force`: Automatically selects the first non-disabled item
Default: false
modelValue
anyEvents
update:model-value
[value: any]Slots
default
SelectRootSlotPropsSelect.Activator
Props
Slots
default
SelectActivatorSlotPropsSelect.Content
Props
Slots
default
SelectContentSlotPropsSelect.Cue
Props
Slots
default
SelectCueSlotPropsSelect.HiddenInput
Props
Select.Item
Props
Slots
default
SelectItemSlotProps<V>Select.Placeholder
Props
Slots
default
SelectPlaceholderSlotPropsSelect.Value
Props
Slots
default
SelectValueSlotPropsSelection.Item
Props
Slots
default
SelectionItemSlotProps<V>Recipes
Form Submission
Set name on Root to auto-render hidden inputs for form submission — one per selected value in multi-select mode:
<template>
<Select.Root v-model="value" name="color">
<!-- ... -->
</Select.Root>
</template>Mandatory Selection
Use mandatory to prevent deselecting the last item, or mandatory="force" to auto-select the first item on mount:
<template>
<Select.Root v-model="value" mandatory="force">
<!-- First non-disabled item is selected automatically -->
</Select.Root>
</template>Custom Positioning
Control dropdown placement with CSS anchor positioning props on Content:
<template>
<Select.Content position-area="top" position-try="flip-block">
<!-- Dropdown appears above the activator -->
</Select.Content>
</template>Data Attributes
Style interactive states without slot props:
| Attribute | Values | Component |
|---|---|---|
data-selected | true | Item |
data-highlighted | "" | Item |
data-disabled | true | Item |
data-select-open | "" | Activator |
data-state | "open" / "closed" | Cue |