# @stackline/vue-multiselect-dropdown > Maintained Vue multiselect dropdown with Vue 2 and Vue 3 release lines, accessibility-focused keyboard/ARIA tested behavior, Stackline skins, body overlays, object data, scoped slots, and renderless composables. [![npm version](https://img.shields.io/npm/v/@stackline/vue-multiselect-dropdown.svg?style=flat-square)](https://www.npmjs.com/package/@stackline/vue-multiselect-dropdown) [![license](https://img.shields.io/npm/l/@stackline/vue-multiselect-dropdown.svg?style=flat-square)](https://github.com/alexandroit/vue-multiselect-dropdown) [![GitHub repository](https://img.shields.io/badge/GitHub-repository-181717?style=flat-square&logo=github)](https://github.com/alexandroit/vue-multiselect-dropdown) [![Docs](https://img.shields.io/badge/docs-alexandro.net-0f766e?style=flat-square)](https://alexandro.net/docs/vue/multiselect/vue-3/) [![Reddit community](https://img.shields.io/badge/community-r%2FStackline-ff4500?style=flat-square&logo=reddit&logoColor=white)](https://www.reddit.com/r/Stackline/) **[Documentation](https://alexandro.net/docs/vue/multiselect/vue-3/)** | **[npm](https://www.npmjs.com/package/@stackline/vue-multiselect-dropdown)** | **[Issues](https://github.com/alexandroit/vue-multiselect-dropdown/issues)** | **[Repository](https://github.com/alexandroit/vue-multiselect-dropdown)** **Current package version:** `3.1.8` --- ## Why this package? `@stackline/vue-multiselect-dropdown` provides maintained Vue 2 and Vue 3 multiselect release lines for applications that need predictable selection state, search, grouping, skins, keyboard support, custom Vue markup, and live tested examples. The package follows the familiar Stackline settings contract while staying idiomatic for Vue: pass `:data`, bind selected values with `v-model`, customize behavior through `:settings`, and listen for Vue events such as `@select`, `@de-select`, `@select-all`, `@de-select-all`, `@open`, and `@close`. This README documents Vue 3 package `3.1.6`. It preserves the renderless and state composables, scoped-slot customization, helper APIs, and combobox contract while refreshing the tested Vue runtime and security-sensitive development toolchain without changing the public API. ## Compatibility | Item | Value | | --- | --- | | Package | `@stackline/vue-multiselect-dropdown@3.1.8` | | Peer: `vue` | `>=3.0.0 <4.0.0` | | Runtime dependencies | None; framework peers are supplied by the application | | Package format | ESM, CommonJS, and TypeScript declarations | ### Vue Version Compatibility Each package family installs on its matching Vue family. Keep the package family aligned with the Vue major used by your application. | Package family | Vue family | Peer range | Tested release window | Demo link | | :---: | :---: | :---: | :---: | :--- | | **3.x** | **Vue 3 only** | **`>=3.0.0 <4.0.0`** | **3.1.5 through 3.5.41** | [Vue 3 family docs](https://alexandro.net/docs/vue/multiselect/vue-3/) | | **2.x** | **Vue 2 only** | **`>=2.0.0 <3.0.0`** | **2.0.2** | [Vue 2 family docs](https://alexandro.net/docs/vue/multiselect/vue-2/) | ## Installation For Vue 3 applications: ```bash npm install @stackline/vue-multiselect-dropdown@3.1.8 --save-exact ``` For Vue 2 applications: ```bash npm install @stackline/vue-multiselect-dropdown@2.0.2 --save-exact ``` The styled component injects its component styles at runtime. The renderless composables do not require the built-in DOM and let your application own the markup and styling. ## Usage ### Setup #### 1. Import the component ```js import { createApp } from 'vue'; import { VueMultiselect, VueMultiselectDropdown } from '@stackline/vue-multiselect-dropdown'; ``` #### 2. Keep selection in Vue state ```js const selectedCountries = ref([]); ``` #### 3. Pass a stable settings object ```js const settings = { text: 'Select countries', enableSearchFilter: true, primaryKey: 'id', labelKey: 'itemName', badgeShowLimit: 3, skin: 'classic' }; ``` Register globally: ```js const app = createApp(App); app.use(VueMultiselect); app.mount('#app'); ``` Or register locally: ```js export default { components: { VueMultiselectDropdown } }; ``` ### Basic Usage ```vue ``` ## Features | Feature | Supported | | :--- | :---: | | Vue 2 and Vue 3 maintained release lines | Yes | | Multi-select and single-select modes | Yes | | Controlled `v-model` selection | Yes | | Scoped slots for custom Vue HTML | Yes | | Renderless `useMultiSelectDropdown` composable | Yes | | State-only `useMultiSelectState` composable | Yes | | Search and filter | Yes | | Group by field or callback | Yes | | Custom item render functions | Yes | | Custom badge render functions | Yes | | Lazy loading hooks | Yes | | Add-new-item from search text | Yes | | Instance methods for open, close, focus, select all, and clear | Yes | | Built-in `classic`, `material`, `dark`, `custom`, and `brand` skins | Yes | | Accessibility-focused and keyboard/ARIA tested navigation, focus states, and ARIA labels | Yes | | Multiselect options expose both `aria-selected` and `aria-checked` | Yes | | Backspace/Escape combobox contract | Yes | | Selected object preservation across async data refreshes | Yes | | Dialog and overflow-container support through `appendToBody` / `tagToBody` | Yes | | Left-aligned, vertically centered placeholder and single-value text | Yes | | Versioned docs builds per Vue line | Yes | ## Security See the [security policy](https://github.com/alexandroit/vue-multiselect-dropdown/blob/main/SECURITY.md) for supported release lines and private vulnerability reporting. Keep framework peers and application dependencies patched. Dependency audits cover known advisories; they do not establish that an application is secure. ## API Surface ### Styling and Skins Use `settings.skin` to switch the visual mode: ```js settings.skin = 'material'; ``` Built-in skins: | Skin | Usage | | :--- | :--- | | `classic` | Compact classic dropdown styling. | | `material` | Material-style rounded controls and chips. | | `dark` | Dark UI surfaces. | | `custom` | CSS-variable starter skin for custom projects. | | `brand` | Stackline brand skin. | `settings.theme` is accepted as a legacy alias, but new Vue usage should configure only `settings.skin`. ### Customization Paths Use the API layer that matches the amount of control your team needs: | Layer | Best for | What you own | | :--- | :--- | :--- | | `` | Fast forms, filters, dashboards, and admin screens. | Data, selected state, settings, events, and optional render functions. | | `createVueMultiselectDropdown()` | Teams that want a typed helper around settings and composables. | Typed state/composable usage for a feature or design-system wrapper. | | Scoped slots | Custom Vue HTML around the proven component behavior. | Specific DOM pieces such as trigger, badges, menu footer, group headers, and options. | | `useMultiSelectDropdown` | Fully custom interfaces and design systems. | All markup and CSS, while Stackline provides state, ARIA prop bags, keyboard flow, grouping, and callbacks. | | `useMultiSelectState` | Advanced state engines or existing combobox shells. | Every element, every event binding, and all visual behavior. | For most teams, start with the component. Use scoped slots when the component works but your layout needs a different shell. Use the renderless composables when the application must own the whole combobox contract. ### Typed Helper API Use `createVueMultiselectDropdown()` when a feature, package, or design-system wrapper should bind the item type once and reuse it across settings and composables. This is optional. The normal `` API remains the fastest path for most screens. ```ts import { createVueMultiselectDropdown } from '@stackline/vue-multiselect-dropdown'; type Country = { id: number; itemName: string; capital: string; region: string; }; const CountryMultiselect = createVueMultiselectDropdown(); const countrySettings = CountryMultiselect.defineSettings({ text: 'Choose countries', primaryKey: 'id', labelKey: 'itemName', searchBy: ['itemName', 'capital'], groupBy: 'region', enableSearchFilter: true, badgeShowLimit: 2 }); export function useCountryMultiselect(countries, selectedCountries) { return CountryMultiselect.useMultiSelectDropdown({ data: countries, selectedItems: selectedCountries, settings: countrySettings, onChange(items) { selectedCountries.value = items; } }); } ``` The helper is useful when a design-system wrapper wants one typed place for settings, state, and renderless dropdown behavior. The styled Vue component remains available for normal template usage. ### Scoped Slots Scoped slots let you replace the visible Vue HTML pieces while the package still owns the tested selection, filtering, keyboard, focus, ARIA, body overlay, and async behavior. The important rule is simple: keep the provided slot callbacks and ARIA values connected to your custom elements. They carry the behavior that makes the component accessible. ```vue ``` Available scoped slots: `trigger`, `placeholder`, `badge`, `option`, `loading`, `empty`, `group-header`, and `menu-footer`. ### Headless Usage Use `useMultiSelectDropdown` when you want Stackline selection, filtering, keyboard handling, ARIA prop bags, grouping, limits, and callbacks without the built-in DOM/CSS. The flag sample below uses SVG country icons from `flag-icons`. You can replace that import with your own icon system if your app already has one. ```bash npm install flag-icons ``` ```vue ``` Use `useMultiSelectState` when you want the selection/filter/grouping engine without prop bags: ```js import { useMultiSelectState } from '@stackline/vue-multiselect-dropdown'; const state = useMultiSelectState({ data: countries, selectedItems, onChange(items) { selectedItems.value = items; }, settings: { primaryKey: 'id', labelKey: 'itemName', enableSearchFilter: true } }); ``` The styled component remains available for drop-in usage. The renderless composables are for teams that want a headless-style ownership model where the application controls layout, elements, and CSS. ### Combobox Contract The `3.1.x` line enforces the interaction details that usually matter most in production forms: | Behavior | Contract | | :--- | :--- | | Focus after selection/removal | Focus returns to search while the list stays open, or to the trigger when the list closes. | | Option selection state | Multiselect options expose matching `aria-selected` and `aria-checked` values. | | Backspace in search | Edits the query. With an empty query it does not remove selected values by default. | | Backspace/Delete on focused badge remove button | Removes that selected badge. | | Escape | Closes the list without clearing selected values. | | Async option refresh | Selected object values stay stable by `primaryKey` when data refreshes. | | Keyboard navigation | Trigger ArrowDown/ArrowUp, option Home/End, and option ids keep focus and ARIA predictable. | Keyboard behavior is enabled by default. You can turn each part off from `settings.keyboard` when an application needs a stricter interaction model: ```js const settings = { text: 'Countries', enableSearchFilter: true, keyboard: { space: true, spaceOptionAction: 'toggle', tab: true, arrows: true, escape: true, backspaceRemovesLastWhenSearchEmpty: false, deleteRemovesFocusedBadge: true } }; ``` Set any key to `false` to disable that behavior. `backspaceRemovesLastWhenSearchEmpty` can be turned on for applications that want the legacy "empty search removes last badge" pattern. `keyboard.backspace` is still accepted as a deprecated alias for that legacy behavior. `spaceOptionAction` controls only focused options: `'toggle'` keeps focus on the current option, while `'toggle-and-next'` toggles and moves to the next enabled option. `escapeToClose: false` is still supported and also disables `keyboard.escape`. ### Official Vue 3 Test Matrix The Vue 3 release was tested in a clean Vue `3.5.41` application with `@stackline/vue-multiselect-dropdown@3.1.8`. The docs use the same examples from that test app, including keyboard navigation, focus, ARIA behavior, badge counters, responsive action buttons, scrollable lists, dialog-safe body overlays, the corrected left-aligned placeholder with vertical centering, scoped-slot customization, headless/custom HTML, and the combobox contract checks for Backspace, Escape, focused badge removal, focus, and option ARIA. The same core scenarios are validated for the visual skins: | # | Scenario | Main settings tested | | :---: | :--- | :--- | | 01 | Basic multi | `{ enableSearchFilter: true }` | | 02 | All selected badges visible | `{ badgeShowLimit: 10 }` | | 03 | Single selection | `{ singleSelection: true }` | | 04 | Search by fields | `{ searchBy: ['itemName', 'capital'] }` | | 05 | Grouped options | `{ groupBy: 'category', selectGroup: true }` | | 06 | Selection limit | `{ limitSelection: 2, badgeShowLimit: 2 }` | | 07 | Custom rendering | `renderItem` and `renderBadge` | | 08 | Search and add item | `{ addNewItemOnFilter: true }` | | 09 | Disabled state | `{ disabled: true }` | | 10 | Controlled form validation | Vue state and derived validation | | 11 | Long list with keyboard scroll | `{ maxHeight: 140 }` | | 12 | Local lazy loading | `{ lazyLoading: true }` | | 13 | Dialog and overflow container | `{ appendToBody: true, tagToBody: true }` | | 14 | Body overlay auto direction | `{ autoPosition: true, position: 'top' }` | | 15 | Instance methods | `openDropdown`, `closeDropdown`, `selectAll`, `clearSelection` | | 16 | Scoped slots custom HTML | `#trigger`, `#option`, `#badge`, `#group-header`, `#menu-footer` | ### Custom Render Functions Use `renderItem` for option rows and `renderBadge` for selected chips when you only need to replace inner content. Use scoped slots when you need to replace component structure. ```vue ``` ### Forms and Controlled State Keep the selected array in Vue state and derive validity from that state: ```vue ``` ### Lazy Loading and Dynamic Data Enable lazy loading through the settings object and append more rows when the list reaches the end: ```vue ``` ### Dialogs and Overflow Containers Use `appendToBody: true` or `tagToBody: true` when the dropdown is inside dialogs, modals, drawers, or containers that set `overflow: hidden` or `overflow: auto`. ```js const settings = { text: 'Dialog dropdown', enableSearchFilter: true, skin: 'material', appendToBody: true, tagToBody: true, autoPosition: true }; ``` With body overlay enabled, the open panel is rendered against `document.body`, aligned to the original trigger, sized to the trigger, recalculated on open, scroll, resize, and content changes, and cleaned up when the dropdown closes or the component unmounts. `autoPosition: true` treats `position` as a preferred direction. The menu opens upward only when there is meaningfully less room below and enough room above; otherwise it opens below and shrinks the scrollable list height to stay visible without covering the trigger. ### Events Available Vue events: - `input` - `change` - `select` - `de-select` - `select-all` - `de-select-all` - `group-select` - `group-de-select` - `scroll-to-end` - `add-filter-new-item` - `open` - `close` ### Instance Methods ```vue ``` Available methods: - `openDropdown()` - `closeDropdown()` - `focusSearch()` - `selectAll()` - `deSelectAll()` - `clearSelection()` - `getSelectedItems()` ## Local Development Use Node `24.20.0` and npm `11.19.0` for reproducible release artifacts. ```bash npm ci npm run build npm test ``` Vue 3 docs: ```bash cd docs-src/vue-3 npm ci npm run build ``` ## Consumer Smoke Test `npm run check` builds the library and checks its public package contents and existing behavior. The existing tests load the built package exports; use the documented peer range when installing a packed tarball into a separate application. ## Release Checklist - Run `npm ci`, `npm run check`, and the applicable browser or consumer checks. - Review `npm audit` and `npm audit --omit=dev` separately. - Review the packed README, declarations, exports, license, and compatibility metadata. - Publish through the [GitHub Actions workflow](https://github.com/alexandroit/vue-multiselect-dropdown/actions/workflows/publish.yml) using the tested artifact's SHA-512 digest. - Verify the exact npm tarball, version, and GitHub provenance after publication; never replace a published version. ## License MIT ## Credits and original authors - Copyright (c) 2026 Stackline. - Stackline maintenance: [Alexandro Paixao Marques](https://www.linkedin.com/in/aleinfo/) and [Stackline contributors](https://github.com/alexandroit). Original copyright, license notices and contributor acknowledgements remain part of this distribution. Stackline maintenance does not replace authorship of the original work. ## Community and Links - [Stackline website](https://alexandro.net/) - [GitHub projects](https://github.com/alexandroit) - [npm packages](https://www.npmjs.com/~alex360qc) - [Reddit community — r/Stackline](https://www.reddit.com/r/Stackline/) - [Maintainer LinkedIn](https://www.linkedin.com/in/aleinfo/) Use this repository's issue tracker for reproducible bugs and feature requests. Join r/Stackline for examples, usage questions and release discussions.