Examples
Single select
Selected: null
<VSelect
v-model="fruit"
:options="fruits"
label="Pick a fruit"
placeholder="Choose…"
clearable
/>Multi select with tags
Selected: ["apple","banana"]
<VSelect v-model="favorites" :options="fruits" label="Favorites" multiple clearable />Tag-remove buttons are reachable via Tab and labelled "Remove {item}". Backspace in an empty search input removes the last tag.
Searchable (client-side)
<VSelect v-model="value" :options="options" searchable placeholder="Type to filter…" />Async / server-side search
Search is debounced 300 ms and gated to 2 characters. aria-busy flips while loading.
<script setup lang="ts">
import { ref } from 'vue'
import { VSelect } from '@vue-select-plus/vue'
const value = ref<string | null>(null)
const options = ref<SelectOption[]>([])
const loading = ref(false)
let token = 0
async function onSearch(query: string) {
const my = ++token
if (!query) { options.value = []; return }
loading.value = true
const data = await fetch(`/api/users?q=${encodeURIComponent(query)}`).then(r => r.json())
if (my !== token) return // race-guard: ignore stale responses
options.value = data.map(u => ({ value: u.id, label: u.name }))
loading.value = false
}
</script>
<template>
<VSelect
v-model="value"
:options="options"
:loading="loading"
:filterable="false"
:min-search-length="2"
:search-debounce="300"
searchable
@search="onSearch"
/>
</template>Nested tree
Selected: ["vue"]
Use →/← to expand/collapse, or jump to a parent.
<VSelect v-model="stack" :options="techStack" multiple searchable label="Tech stack" />const techStack = [
{
label: 'Frontend', value: 'fe',
children: [
{ label: 'Vue', value: 'vue' },
{ label: 'React', value: 'react' }
]
},
{
label: 'Backend', value: 'be',
children: [
{ label: 'Node.js', value: 'node', children: [...] },
{ label: 'Go', value: 'go' }
]
}
]Creator mode
Hover any group with children — a + button appears.
<VSelect v-model="value" :options="options" creatable @create="handleCreate" />The + handles only render when creatable is set. Listening to @create alone isn't enough — the prop is the explicit opt-in.
function handleCreate({ parent, value }: { parent: string | number; value: string }) {
// mutate your tree however you like — push to children, call an API, etc.
}Validation + error state
Please pick a fruit.
<VSelect
v-model="value"
:options="options"
required
:error="value ? '' : 'Please pick a fruit.'"
/>The combobox reports aria-invalid="true" and links to the visible message via aria-describedby.
Native form integration
Submit to see the serialized form data.
<form @submit="onSubmit">
<VSelect v-model="stack" :options="techStack" name="stack" multiple />
<button type="submit">Submit</button>
</form>function onSubmit(e: Event) {
e.preventDefault()
const data = new FormData(e.target as HTMLFormElement)
console.log(data.getAll('stack')) // ['vue', 'go']
}When name is set, the component emits hidden <input>s — one per selected value in multi mode — so the form serializes exactly like a native <select>.
Virtualized list (5 000 items)
<VSelect v-model="value" :options="hugeList" searchable />The dropdown only mounts the rows currently in view — performance stays flat regardless of list size.
Theming
:root {
--vs-primary: #16a34a;
--vs-radius: 12px;
}
:root.dark {
--vs-primary: #4ade80;
}See the CSS variables reference for the full list.