voo-select

Vue 3 port of bootstrap-select: multiselect, search and virtual scrolling for Bootstrap selects, with zero jQuery.
Install
npm install voo-select
Latest Version:0.5.1
Vue:3
License:MIT
Last Updated:Oct 23, 2026
Links: npm
Maintainer: alrico88

voo-select

Vue 3 port of bootstrap-select with zero jQuery.

Disclaimer: This is a personal exploration project. I am not responsible for any malfunction or issues that may arise from its use.

Installation

pnpm add voo-select

Peer dependencies:

  • vue ^3.5.0
  • bootstrap >=5.0.0

Usage

<script setup lang="ts">
import { ref } from "vue";
import { VooSelect, type VooSelectOptionInput } from "voo-select";
import "voo-select/dist/voo-select.css";

const value = ref<string | string[]>();
const options: VooSelectOptionInput[] = [
  "Apple",
  { text: "Banana", value: "banana" },
  { text: "Cherry", value: "cherry", selected: true },
];
</script>

<template>
  <voo-select
    v-model="value"
    :options="options"
    placeholder="Pick one"
  />
</template>

Props

Prop Type Default Description
options VooSelectOptionInput[] [] Option data
modelValue string | number | (string | number)[] — v-model value; emits the option's original value type
multiple boolean false Enable multiple selection
disabled boolean false Disable the select
size 'sm' | 'md' | 'lg' 'md' Button size
menuSize 'auto' | number | false 'auto' Number of visible items in menu
placeholder string | null null Placeholder text
liveSearch boolean false Enable search/filter
liveSearchPlaceholder string | null null Search input placeholder
liveSearchStyle 'contains' | 'startsWith' 'contains' Search matching style
liveSearchNormalize boolean false Accent-insensitive search
style string 'btn-light' Button style class
styleBase string 'btn' Base button class
width 'auto' | 'fit' | string | false false Select width
maxOptions number | false false Max selections in multi-select
loading boolean false Show a loading row in the menu (e.g. lazy fetch on shown)
loadingText string 'Loading…' Text of the loading row
actionsBox boolean false Show select/deselect all buttons
doneButton boolean false Show done button
doneButtonText string 'Close' Done button text
allowClear boolean false Show clear button
selectedTextFormat string 'values' Display format: 'values', 'static', 'count'
countSelectedText function — Format function for count display
multipleSeparator string ', ' Separator for selected items
selectAllText string 'Select All' Select all button text
deselectAllText string 'Deselect All' Deselect all button text
noneSelectedText string 'Nothing selected' Text when nothing selected
noneResultsText string 'No results matched {0}' Text when no search results
showSubtext boolean false Show subtext in button
showIcon boolean true Show icons in button
showContent boolean true Show custom HTML content
showTick boolean false Checkmark on single-select
iconBase string '' Icon font base class (e.g. 'fa')
tickIcon string 'voo-select-ok-default' Checkmark icon class
header string | false false Menu header text
dropupAuto boolean true Auto-detect dropup direction
dropdownAlignRight boolean | 'auto' false Right-align dropdown
container string | false false Teleport the menu to this selector (e.g. 'body'); root classes are copied
hideDisabled boolean false Hide disabled options
selectOnTab boolean false Tab selects in dropdown
virtualScroll number | boolean 600 Virtual scroll threshold
sanitize boolean true Enable HTML sanitization
whiteList object — Allowed HTML tags/attributes
sanitizeFn function | null null Custom sanitize function
source object — Data source config (fetching/pagination)
maxOptionsText function | string[] — Text when max selections reached
windowPadding number | number[] 0 Window padding for auto-size
display string | false false Display style for dropdown
template object — Template overrides ({ caret: '<span…>' })
title string | null null Button title while nothing is selected
name string — Form field name (see "Forms" below)
required boolean false Native form validation (see "Forms" below)
locale string 'en' Locale for the default texts

Text props (noneSelectedText, selectAllText, …) default to the active locale; passing one overrides the locale text.

options format

Accepts plain strings, option objects, or group (optgroup) objects:

// Simple string
"Apple"

// Option object
{
  text?: string
  value?: string | number
  html?: string
  selected?: boolean
  disabled?: boolean
  title?: string
  icon?: string
  subtext?: string
  tokens?: string
  className?: string
  style?: string
  divider?: boolean
}

// Optgroup
{
  label: string
  disabled?: boolean
  className?: string
  children?: VooSelectOptionInput[]
  options?: VooSelectOptionInput[] // alias for children
}

Slots

Use scoped slots to render Vue components instead of HTML strings. Each slot is optional; omitted slots use the existing HTML/text rendering.

<template lang="pug">
voo-select(v-model="value" :options="options")
  template(#option="{ option, selected, active, disabled }")
    my-option(:label="option.text" :selected="selected" :active="active" :disabled="disabled")
  template(#selected-option="{ option }")
    span.badge.text-bg-primary {{ option.text }}
  template(#optgroup="{ group }")
    strong {{ group.display }}
</template>
Slot Props Replaces
option { option, index, selected, active, disabled } Option icon, HTML/text and subtext in the menu
selected-option { option, index } Each selected option's title, icon, HTML/text and subtext in the button
optgroup { group, index } Group label and subtext

option and group are normalized OptionData records; group.display contains the sanitized group label. Treat slot props as read-only. Menu indexes include group headers/dividers and refer to the filtered list; selected-option indexes refer to the options displayed in the button. The exported VooSelectSlots, OptionSlotProps, SelectedOptionSlotProps and OptgroupSlotProps types describe these contracts.

Slots take precedence over HTML/text and the showIcon, showContent and showSubtext display flags. The button still respects placeholder, static/count formatting, separators and the 50-option display limit. The select keeps the option container, selection tick, keyboard navigation and ARIA state; use presentational content inside options rather than nested buttons, links or inputs.

Keep supplying text, value and optional tokens: search, keyboard typeahead, the button tooltip and native form submission use option data, not the rendered slot content. Vue slot content is rendered normally; the HTML sanitizer still applies to HTML strings, not to your Vue components or any v-html you add yourself.

Virtual lists mount slot components only for the rendered rows and measure their actual heights, including reactive size changes. Offscreen components can unmount; keep persistent state in the parent, keyed by option value.

Forms

With name or required, a hidden native <select> carries the value into <form> submissions (FormData) and native validation (checkValidity()). Only the selected options are mirrored into it, so large lists don't double the DOM size. Without name or required it isn't rendered.

Methods

Exposed through a template ref:

<script setup>
import { ref } from "vue";
const picker = ref();
picker.value?.val(["a", "b"]);
</script>

<template>
  <voo-select
    ref="picker"
    multiple
  />
</template>
Method Description
val(value?) Get the current value, or set it
selectAll() Select all visible options (respects limits)
deselectAll() Deselect all visible options
refresh() Rebuild the options from the options prop
open() Open the menu
close() Close the menu
toggle() Toggle the menu

Events

Event Payload Description
loaded — Fires after initialization
rendered — Fires after button text is re-rendered
refreshed — Fires after options are refreshed
changed ChangedArguments Fires after value changes
fetched — Fires after data is fetched
show Event Fires when dropdown is shown
shown Event Fires after dropdown is shown
hide Event Fires when dropdown is hidden
hidden Event Fires after dropdown is hidden
maxReached — Fires when max options is reached
maxReachedGrp — Fires when optgroup max is reached
update:modelValue value v-model update

Styles

import "voo-select/dist/voo-select.css";

Bootstrap CSS must be loaded separately (either via CDN or your own import).

Development

pnpm install
pnpm dev        # Demo page with HMR
pnpm typecheck  # TypeScript check
pnpm build      # Typecheck + build library
pnpm test       # Run tests

The main demo includes a Vue slots playground section (/#slots) with reactive Vue components, multiple selection, Teleport, forms, HTML fallbacks and 10,000 virtual options.

Related Packages

vue3-extended-multiselect

Extended multiselect dropdown component for Vue 3 with search, tagging, async op...

3,578 7
vue-extended-multiselect

Extended multiselect dropdown component for Vue 2 with search, tagging, async op...

1,593 1
slim-select_2

Slim advanced select dropdown

2,800 1,332
slim-select-with-styles

Slim advanced select dropdown

1,642 1,332
vue-searchable-select

Pure Vue3 version of the 'searchable select box' widget

11,372 0

Version History

Version Released Vue License
0.5.1 ^3.5.0 MIT
0.5.0 ^3.5.0 MIT
0.4.5 ^3.5.0 MIT
0.4.4 ^3.5.0 MIT
0.4.3 ^3.5.0 MIT
Pre-releases (1)
Version Released License
0.0.0-stage