Select
Headless dropdown select with single and multi-selection, keyboard navigation, and 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
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
Composable selection state in v0
vuestateNative popovers and CSS anchor positioning
cssbrowserType-safe provide and inject
vuetypescriptVirtual focus for accessible listboxes
a11yvueTree-shaking the utility barrel
typescriptbuildStyling headless components with data attributes
cssa11y
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>Understanding id vs value
Each Select.Item has two key props:
id— Internal key for the selection registry. Used for virtual focus, ARIA attributes, and ticket lookup.value— The value synced tov-model. This is whatSelect.Value’sselectedValueslot prop exposes.
The model always receives the value prop, not the id. When id and value differ, use the selectedValue slot prop to look up a display label:
<script setup lang="ts">
import { Select } from '@vuetify/v0'
import { shallowRef } from 'vue'
const language = shallowRef('en')
const languages = [
{ id: 'en', label: 'English' },
{ id: 'es', label: 'Spanish' },
{ id: 'fr', label: 'French' },
]
</script>
<template>
<Select.Root v-model="language" mandatory>
<Select.Activator>
<Select.Value v-slot="{ selectedValue }">
{{ languages.find(l => l.id === selectedValue)?.label }}
</Select.Value>
<Select.Cue />
</Select.Activator>
<Select.Content>
<Select.Item
v-for="lang in languages"
:id="lang.id"
:key="lang.id"
:value="lang.id"
>
{{ lang.label }}
</Select.Item>
</Select.Content>
</Select.Root>
</template> When id and value are the same (the common case), Select.Value displays the model value directly — no lookup needed.
Pre-Selected Values
Select supports pre-selected values via v-model or :model-value. The Select.Value component shows the model value immediately, even before the dropdown has been opened. Select.Placeholder automatically hides when a model value is present:
<template>
<!-- "Banana" shows immediately, no dropdown open needed -->
<Select.Root v-model="fruit" mandatory>
<Select.Activator>
<Select.Value v-slot="{ selectedValue }">{{ selectedValue }}</Select.Value>
<Select.Placeholder>Pick a fruit…</Select.Placeholder>
</Select.Activator>
<Select.Content>
<Select.Item value="Apple">Apple</Select.Item>
<Select.Item value="Banana">Banana</Select.Item>
</Select.Content>
</Select.Root>
</template>Data Attributes
Style interactive states without slot props:
| Attribute | Values | Component |
|---|---|---|
data-selected | true | Item |
data-highlighted | "" | Item |
data-disabled | true | Item |
data-open | true | Activator |
data-state | "open" / "closed" | Cue |
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 |
FAQ
id is the internal registry key used for virtual focus, ARIA wiring, and ticket lookup; value is what syncs to v-model. When they differ, use the selectedValue slot prop on Select.Value to map the model value to a display label.
Add multiple to Select.Root and bind v-model to an array. The dropdown stays open after each pick so the user can stack several values in one pass.
Use Select when users pick from a fixed list without typing. For free-text entry or typeahead filtering that can create new options, reach for Combobox instead.
Add mandatory to Select.Root to block deselecting the last item, or mandatory="force" to also auto-select the first non-disabled item on mount.
Set name on Select.Root and it auto-renders one hidden input per selected value, so the field posts with the form without placing a hidden-input sub-component by hand.