Skip to content

Examples

Single select

Choose…Choose…

Selected: null

vue
<VSelect
    v-model="fruit"
    :options="fruits"
    label="Pick a fruit"
    placeholder="Choose…"
    clearable
/>

Multi select with tags

AppleBananaApple, Banana, 2 items selected

Selected: ["apple","banana"]

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

Type to filter…
vue
<VSelect v-model="value" :options="options" searchable placeholder="Type to filter…" />
Type at least 2 chars…

Search is debounced 300 ms and gated to 2 characters. aria-busy flips while loading.

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

VueVue, 1 item selected

Selected: ["vue"]

Use / to expand/collapse, or jump to a parent.

vue
<VSelect v-model="stack" :options="techStack" multiple searchable label="Tech stack" />
ts
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 a group, hit +Hover a group, hit +

Hover any group with children — a + button appears.

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

ts
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

Select...Select...

Please pick a fruit.

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

VueVue, 1 item selected

Submit to see the serialized form data.

vue
<form @submit="onSubmit">
    <VSelect v-model="stack" :options="techStack" name="stack" multiple />
    <button type="submit">Submit</button>
</form>
ts
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)

Select...
vue
<VSelect v-model="value" :options="hugeList" searchable />

The dropdown only mounts the rows currently in view — performance stays flat regardless of list size.

Theming

css
:root {
    --vs-primary: #16a34a;
    --vs-radius: 12px;
}
:root.dark {
    --vs-primary: #4ade80;
}

See the CSS variables reference for the full list.