If you've worked with Vue, chances are you came across vue-select. With over 4.6 million weekly downloads, it became the default select component for the Vue ecosystem. It handled filtering, multi-select, tagging, and async search out of the box. For most of Vue 2's lifetime, it just worked.
Then Vue 3 arrived, and vue-select didn't follow. The v4 beta landed in November 2022 targeting Vue 3, but it never reached a stable release. Over two years later, the beta is still the latest version. Issues pile up. PRs go unreviewed. Thousands of projects are stuck on a component that's effectively abandoned.
I don't say this to criticize the maintainer -- maintaining a popular open source project solo is brutally hard, and vue-select served the community well for years. But the reality is clear: if you're building a new Vue 3 app today, vue-select is not a reliable choice.
The pain points
If you've tried to use vue-select with Vue 3, you've probably hit these:
No stable Vue 3 release. The v4 beta works for basic cases but has unresolved issues with reactivity, slots, and TypeScript support.
Opinionated CSS that fights your design system. vue-select ships over 400 lines of CSS. If your project uses Tailwind, a custom design system, or anything beyond the default look, you're overriding styles constantly.
No TypeScript generics. In a world where ref<User>() and defineProps<{ items: User[] }>() are standard, vue-select's prop types are loose at best.
Introducing vue-superselect
I built vue-superselect to solve all three. It's a headless, accessible, TypeScript-first select/combobox component for Vue 3.
npm install vue-superselect
Headless means it ships zero CSS. You bring your own styles -- classes, Tailwind, CSS-in-JS, whatever your project uses. No more fighting a component's built-in stylesheet.
<scriptsetuplang="ts">import{ref}from'vue'import{SelectRoot,SelectControl,SelectInput,SelectContent,SelectOption,}from'vue-superselect'constselected=ref<string|null>(null)constfruits=['Apple','Banana','Cherry','Grape','Orange']</script><template><SelectRootv-model="selected"><SelectControl><SelectInputplaceholder="Pick a fruit..."/></SelectControl><SelectContent><SelectOptionv-for="fruit in fruits":key="fruit":value="fruit":label="fruit">{{fruit}}</SelectOption></SelectContent></SelectRoot></template>
Two APIs, one library
vue-superselect gives you two ways to build selects, depending on how much control you need.
Compound components are the primary API. They use Vue's provide/inject to share state, so you compose them declaratively in your template. This is the approach most projects should start with -- it's concise, readable, and handles all the ARIA attributes automatically.
The composable API (useSelect()) is for when you need full control over the rendered DOM. Instead of components, you get prop getter functions that return the right attributes for each element. You spread them onto your own <input>, <ul>, and <li> elements with v-bind. This is useful for design system integrations, custom animations, or when you want to render something the compound components don't support.
<scriptsetuplang="ts">import{onMounted,onBeforeUnmount}from'vue'import{useSelect}from'vue-superselect'importtype{CollectionItem}from'vue-superselect'constfruits=['Apple','Banana','Cherry','Grape','Orange']const{getRootProps,getInputProps,getListboxProps,getOptionProps,isOpen,value,visibleItems,registerItem,unregisterItem,}=useSelect<string>()constitems:CollectionItem<string>[]=fruits.map((fruit,i)=>({id:`fruit-${i}`,value:fruit,label:fruit,disabled:false,element:null,}))onMounted(()=>items.forEach(registerItem))onBeforeUnmount(()=>items.forEach((item)=>unregisterItem(item.id)))</script><template><divv-bind="getRootProps()"><inputv-bind="getInputProps({ placeholder: 'Search fruits...' })" />
<ulv-if="isOpen"v-bind="getListboxProps()"><liv-for="item in visibleItems":key="item.id"v-bind="getOptionProps(item)">{{item.label}}</li></ul></div></template>
Multi-select with tags
Multi-select is a first-class feature. Set multiple on SelectRoot and the v-model becomes an array. Add SelectTag for removable tags, and hide-selected to filter out already-chosen options from the dropdown.
<scriptsetuplang="ts">import{ref}from'vue'import{SelectRoot,SelectControl,SelectInput,SelectContent,SelectOption,SelectTag,}from'vue-superselect'constselected=ref<string[]>([])constskills=['Vue.js','TypeScript','React','Node.js','Python','Go']</script><template><SelectRootv-model="selected"multiplehide-selected><SelectControlv-slot="{ selectedItems, removeItem }">
<SelectTagv-for="item in selectedItems":key="String(item.value)":value="item.value":label="item.label"@remove="removeItem"/><SelectInputplaceholder="Select skills..."/></SelectControl><SelectContent><SelectOptionv-for="skill in skills":key="skill":value="skill":label="skill">{{skill}}</SelectOption></SelectContent></SelectRoot></template>
TypeScript all the way down
vue-superselect is built in TypeScript strict mode with full generic inference. When you pass typed items, the v-model value, slot props, and event payloads are all correctly typed. No any, no manual casting.
interfaceUser{id:numbername:stringrole:'admin'|'member'}// v-model is correctly typed as number | null// SelectOption slot props know about User
What else?
The full feature list:
Accessible by default -- WAI-ARIA combobox pattern, full keyboard navigation, screen reader announcements via a live region
Dropdown positioning -- optional @floating-ui/vue integration for auto-flip and teleport support
Tree-shakeable -- sideEffects: false, ESM + CJS dual builds, /*#__PURE__*/ annotations on all components
Small -- under 12 kB gzipped for the full library
Coming from vue-select?
There's a migration guide in the docs with a prop mapping table and before/after code examples. The core concepts map naturally: v-model, options, label, reduce all have direct equivalents. The main shift is from built-in styles to bringing your own.
Alternatives
I want to be upfront: this isn't the only option. Radix Vue includes a combobox component, and Headless UI has a listbox. Both are excellent libraries.
vue-superselect is purpose-built for the select/combobox pattern specifically. It's a single-purpose library with a dedicated API for filtering, multi-select with tags, and combobox behaviors. If you're already using Radix Vue or Headless UI in your project, their built-in components might be all you need. If you want a focused solution for the select use case -- especially if you're migrating from vue-select -- this was built for you.
I'd love to hear what you think. If you run into issues, please open a GitHub issue -- I'm actively maintaining this and plan to ship Option Groups, Form Integration, and Async Data Loading in the next releases.