Skip to main content
Vuetify0 v1.0 is here
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

Select

Headless dropdown select with single and multi-selection, keyboard navigation, and native popover positioning.

Edit this page
Report a Bug
Open issues
View on GitHub
Copy Markdown

PreviewRenders elementIntermediateJul 22, 2026

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 ? '&#x25B4;' : '&#x25BE;' }}
        </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

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

Select Architecture

Use controls to zoom and pan. Click outside or press Escape to close.

Select Architecture

Examples

Multi-Select Tag Filter

This example wires a multi-select dropdown to a live list. Adding multiple to Select.Root switches the v-model binding from a single value to an array, and the dropdown stays open after each pick so the user can stack several tags in one pass. The selected tags surface as chips inside the Select.Activator via the selectedValues slot prop on Select.Value, and those same tags drive a reactive filter over the article list below — pick a tag and the list narrows, remove it and the list widens again. When no article carries every active tag, an empty state explains how to recover.

The interesting detail is the relationship between Select.Item’s id and value props. The id is the registry key used for virtual focus and ARIA wiring; the value is what syncs to v-model. Here both are the tag string, which keeps the model array identical to the chip labels and to the filter predicate — no lookup table needed. Filtering, the tag universe, and the AND-match predicate all live in the useTagFilter composable, so the markup component stays declarative. The name prop on Select.Root auto-renders a hidden input per selected value, making the filter submittable inside a real form without ever placing a hidden-input sub-component by hand.

Reach for this pattern whenever a field accepts several independent choices that shape what’s shown elsewhere — tag pickers, faceted search, permission editors. For single-choice fields drop multiple and bind a scalar; for free-text entry that creates new options on the fly, prefer Combobox instead. The selection logic underneath comes from createSelection; the dropdown positioning from Popover.

FileRole
useTagFilter.tsOwns the article corpus, derives the tag universe, and computes the AND-matched results from the active tags
TagFilter.vueRenders the multi-select Select compound, surfacing chips in the activator and binding the tag array via defineModel
tag-filter.vueWires the composable to the component and renders the filtered article list with its empty state
6 of 6 articles
  • Composable selection state in v0

    vuestate
  • Native popovers and CSS anchor positioning

    cssbrowser
  • Type-safe provide and inject

    vuetypescript
  • Virtual focus for accessible listboxes

    a11yvue
  • Tree-shaking the utility barrel

    typescriptbuild
  • Styling 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:

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

vue
<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 to v-model. This is what Select.Value’s selectedValue slot 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:

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

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:

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

AttributeValuesComponent
data-selectedtrueItem
data-highlighted""Item
data-disabledtrueItem
data-opentrueActivator
data-state"open" / "closed"Cue

Accessibility

The Select implements the WAI-ARIA Combobox↗︎ pattern with a listbox popup.

ARIA Attributes

AttributeValueComponent
rolecomboboxActivator
rolelistboxContent
roleoptionItem
aria-expandedtrue / falseActivator
aria-haspopuplistboxActivator
aria-controlslistbox IDActivator
aria-selectedtrue / falseItem
aria-disabledtrueItem (when disabled)
aria-multiselectabletrueContent (when multiple)

Keyboard Navigation

KeyAction
Enter / SpaceOpen dropdown, or select highlighted item
ArrowDownOpen dropdown, or move highlight down
ArrowUpOpen dropdown, or move highlight up
HomeMove highlight to first item
EndMove highlight to last item
EscapeClose dropdown
TabClose dropdown and move focus

FAQ

Discord
Need help? Join our community for support and discussions ↗
Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/