README.md

June 20, 2026 · View on GitHub


Vue 3 Select Component

Vue 3 Select Component

Accessible select for Vue 3. Type-safe, customizable, with a batteries-included Select or headless primitives.

npm version npm downloads GitHub stars

Documentation · Getting Started · Primitives · v0 Migration

v0 users, see the migration guide. v0 documentation remains at v0-vue3-select-component.vercel.app on the v0 branch.

What is Vue 3 Select Component?

Headless select built on two APIs that share the same state machine, accessibility behavior, and TypeScript types:

APIImportBest for
Assembled Selectvue3-select-componentDrop-in select with search, clear, multi-select, and sensible defaults
Primitivesvue3-select-component/primitivesCustom 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-model for single and multi-select
  • End-to-end types that tie option.value to v-model
  • Search and filter with custom filterBy logic (search is on by default on assembled Select)
  • Clear, loading, and disabled states with familiar boolean props
  • Option groups via SelectGroup, SelectGroupLabel, and SelectSeparator primitives
  • Create new options with createItem on assembled Select or SelectRoot
  • Accessible by default following the WAI-ARIA combobox pattern
  • Teleport the menu to document.body by default (Reka UI Popover under the hood)
  • Style your way with optional --vs-* CSS variables, or fully unstyled primitives with data-* hooks
  • Light bundle with Vue 3.5+ and Reka UI as the only runtime dependency

Requirements

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.

<Select
  v-model="selectedTags"
  multiple
  clearable
  :options="options"
  placeholder="Search and select"
/>
Modev-model typeDefault
Singlestring | number | nullnull
Multi(string | number)[][]

Disable search with :searchable="false" when you want a button-only select.

Import paths

ImportDescription
vue3-select-componentAssembled Select (default export)
vue3-select-component/primitivesHeadless primitive components
vue3-select-component/stylesDefault assembled styles (--vs-* variables)
vue3-select-component/styles.cssSame 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:

PackagePathPurpose
vue3-select-componentsrc/Published library
@vue3-select-component/playgroundplayground/Interactive demos
@vue3-select-component/docsdocs/Documentation site (vue3-select-component.vercel.app)

Getting started

  1. Clone the repository and check out v1-dev
  2. Install dependencies: pnpm install
  3. 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

ScriptDescription
pnpm run devWatch-build lib + styles, run playground
pnpm run dev:libWatch-build lib + styles only
pnpm run dev:playgroundPlayground dev server only
pnpm run dev:docsDocumentation dev server
pnpm run docs:devWatch-build lib + docs dev server
pnpm run docs:buildBuild lib + generate static docs
pnpm run buildBuild all workspace packages
pnpm run testRun library tests with coverage
pnpm run lintLint the monorepo

Library build output

OutputDescription
dist/index.es.jsAssembled Select entry
dist/primitives.jsHeadless primitives entry
dist/styles.cssMinified default styles

Contributing guidelines

  • Branchingv1-dev for v1 integration, master for releases, v0 for v0 maintenance
  • Commits — Conventional commits
  • Tests — PRs should include tests and pass pnpm run test and pnpm 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.