README.md
June 20, 2026 · View on GitHub
Vue 3 Select Component
Accessible select for Vue 3. Type-safe, customizable, with a batteries-included Select or headless primitives.
Documentation · Getting Started · Primitives · v0 Migration
v0 users, see the migration guide. v0 documentation remains at v0-vue3-select-component.vercel.app on the
v0branch.
What is Vue 3 Select Component?
Headless select built on two APIs that share the same state machine, accessibility behavior, and TypeScript types:
| API | Import | Best for |
|---|---|---|
Assembled Select | vue3-select-component | Drop-in select with search, clear, multi-select, and sensible defaults |
| Primitives | vue3-select-component/primitives | Custom UX such as infinite scroll, virtualized lists, or non-standard layouts |
The assembled Select is a fixed composition of primitives. It is not a separate implementation.
When defaults are not enough, compose primitives directly or mix both approaches.
Features
- Two-way binding with
v-modelfor single and multi-select - End-to-end types that tie
option.valuetov-model - Search and filter with custom
filterBylogic (search is on by default on assembledSelect) - Clear, loading, and disabled states with familiar boolean props
- Option groups via
SelectGroup,SelectGroupLabel, andSelectSeparatorprimitives - Create new options with
createItemon assembledSelectorSelectRoot - Accessible by default following the WAI-ARIA combobox pattern
- Teleport the menu to
document.bodyby default (Reka UI Popover under the hood) - Style your way with optional
--vs-*CSS variables, or fully unstyled primitives withdata-*hooks - Light bundle with Vue 3.5+ and Reka UI as the only runtime dependency
Requirements
- Vue.js 3.5+
Installation
pnpm add vue3-select-component
npm install vue3-select-component
Quick start
Import the assembled component and its default styles:
<script setup lang="ts">
import { ref } from "vue";
import Select from "vue3-select-component";
import "vue3-select-component/styles";
const selected = ref<string | null>(null);
</script>
<template>
<Select
v-model="selected"
:options="[
{ label: 'A Wizard of Earthsea', value: 'wizard_earthsea' },
{ label: 'Harry Potter and the Philosopher\'s Stone', value: 'harry_potter', disabled: true },
{ label: 'The Lord of the Rings', value: 'the_lord_of_the_rings' },
]"
placeholder="Select a book"
/>
</template>
Styles are opt-in and imported manually (CSP-friendly). You can also import vue3-select-component/styles.css directly.
Multi-select, clear, and search
<Select
v-model="selectedTags"
multiple
clearable
:options="options"
placeholder="Search and select"
/>
| Mode | v-model type | Default |
|---|---|---|
| Single | string | number | null | null |
| Multi | (string | number)[] | [] |
Disable search with :searchable="false" when you want a button-only select.
Import paths
| Import | Description |
|---|---|
vue3-select-component | Assembled Select (default export) |
vue3-select-component/primitives | Headless primitive components |
vue3-select-component/styles | Default assembled styles (--vs-* variables) |
vue3-select-component/styles.css | Same stylesheet (explicit .css subpath) |
Primitives
Compose custom selects from low-level building blocks:
<script setup lang="ts">
import { ref } from "vue";
import {
SelectInput,
SelectListbox,
SelectNoOptions,
SelectOption,
SelectPopover,
SelectRoot,
SelectTrailingIcon,
SelectTrigger,
SelectValue,
} from "vue3-select-component/primitives";
import "vue3-select-component/styles";
const model = ref<string | null>(null);
const options = [
{ value: "js", label: "JavaScript" },
{ value: "ts", label: "TypeScript" },
];
</script>
<template>
<SelectRoot
v-model="model"
:options="options"
searchable
>
<SelectTrigger>
<SelectValue placeholder="Pick a language" />
<SelectInput />
<SelectTrailingIcon />
</SelectTrigger>
<SelectPopover>
<SelectListbox>
<SelectNoOptions>No results</SelectNoOptions>
<SelectOption
v-for="opt in options"
:key="opt.value"
:value="opt.value"
:label="opt.label"
/>
</SelectListbox>
</SelectPopover>
</SelectRoot>
</template>
Available primitives include SelectRoot, SelectTrigger, SelectValue, SelectInput, SelectIcon, SelectTrailingIcon, SelectClear, SelectPopover, SelectListbox, SelectOption, SelectGroup, SelectGroupLabel, SelectSeparator, SelectTag, SelectNoOptions, and SelectCreateItem.
Advanced patterns such as virtualized lists, infinite scroll, and remote data fetching are documented as primitives recipes, not assembled Select APIs. See the complex use cases guide.
TypeScript
Option values and v-model stay in sync through generics:
<script setup lang="ts">
import type { SelectOptionData } from "vue3-select-component";
import { ref } from "vue";
import Select from "vue3-select-component";
import "vue3-select-component/styles";
type UserOption = SelectOptionData<number> & { username: string };
const selectedUser = ref<number | null>(null);
const userOptions: UserOption[] = [
{ label: "Alice", value: 1, username: "alice15" },
{ label: "Bob", value: 2, username: "bob01" },
];
</script>
<template>
<Select
v-model="selectedUser"
:options="userOptions"
:get-option-label="(option) => `${option.label} (${option.username})`"
placeholder="Pick a user"
/>
</template>
See the TypeScript guide for model types, custom option shapes, and primitive generics.
Styling
Import vue3-select-component/styles for the assembled Select, then customize with --vs-* CSS variables. Primitives ship unstyled and expose stable data-* hooks for your design system.
See the styling guide.
Nuxt
Works in Nuxt 3 and 4 with no dedicated module. Import the stylesheet once in nuxt.config.ts:
export default defineNuxtConfig({
css: ["vue3-select-component/styles"],
});
See the Nuxt guide for SSR, SSG, and hydration notes.
Migrating from v0
Still on v0? See the v0 documentation.
See the full migration guide from v0 to v1.
Contributing & development
This repository is a pnpm workspace:
| Package | Path | Purpose |
|---|---|---|
vue3-select-component | src/ | Published library |
@vue3-select-component/playground | playground/ | Interactive demos |
@vue3-select-component/docs | docs/ | Documentation site (vue3-select-component.vercel.app) |
Getting started
- Clone the repository and check out
v1-dev - Install dependencies:
pnpm install - Start local development:
pnpm run dev
pnpm run dev watch-builds the library and runs the playground. The playground resolves library source from src/ for fast feedback during component work.
Workspace scripts
| Script | Description |
|---|---|
pnpm run dev | Watch-build lib + styles, run playground |
pnpm run dev:lib | Watch-build lib + styles only |
pnpm run dev:playground | Playground dev server only |
pnpm run dev:docs | Documentation dev server |
pnpm run docs:dev | Watch-build lib + docs dev server |
pnpm run docs:build | Build lib + generate static docs |
pnpm run build | Build all workspace packages |
pnpm run test | Run library tests with coverage |
pnpm run lint | Lint the monorepo |
Library build output
| Output | Description |
|---|---|
dist/index.es.js | Assembled Select entry |
dist/primitives.js | Headless primitives entry |
dist/styles.css | Minified default styles |
Contributing guidelines
- Branching —
v1-devfor v1 integration,masterfor releases,v0for v0 maintenance - Commits — Conventional commits
- Tests — PRs should include tests and pass
pnpm run testandpnpm run build - Docs — Update
docs/when changing public API or behavior
Releases
Changelog and release notes are on GitHub Releases.
License
MIT Licensed. Copyright (c) Thomas Cazade 2024 - present.