Vue 3 compatible multiselect component with advanced selection, search, tagging, and grouping capabilities
—
Built-in search functionality with configurable filtering, custom search functions, and async option loading support.
Enable search functionality with a text input that filters available options.
/**
* Basic search configuration props
*/
interface BasicSearchProps {
/** Enable/disable search functionality (default: true) */
searchable?: boolean;
/** Enable/disable internal filtering (default: true) */
internalSearch?: boolean;
/** Input placeholder text (default: 'Select option') */
placeholder?: string;
/** Enable spellcheck on search input (default: false) */
spellcheck?: boolean;
}Usage Example:
<template>
<VueMultiselect
v-model="selectedUser"
:options="users"
:searchable="true"
placeholder="Search users..."
label="name"
track-by="id">
</VueMultiselect>
</template>
<script>
export default {
data() {
return {
selectedUser: null,
users: [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com' }
]
}
}
}
</script>Configure how search interacts with selection and dropdown state.
/**
* Search behavior configuration props
*/
interface SearchBehaviorProps {
/** Clear search input after selecting an option (default: true) */
clearOnSelect?: boolean;
/** Preserve search value when component loses focus (default: false) */
preserveSearch?: boolean;
/** Close dropdown after selecting an option (default: true) */
closeOnSelect?: boolean;
/** Hide already selected options from dropdown (default: false) */
hideSelected?: boolean;
}Usage Example:
<template>
<VueMultiselect
v-model="selectedTags"
:options="tags"
:multiple="true"
:searchable="true"
:clear-on-select="false"
:preserve-search="true"
:hide-selected="true"
placeholder="Search and select tags">
</VueMultiselect>
</template>Implement custom filtering and sorting logic for search results.
/**
* Custom search configuration props
*/
interface CustomSearchProps {
/**
* Custom function to generate display labels for options
* @param option - The option object
* @param label - The label property name
* @returns Formatted display string
*/
customLabel?: (option: any, label: string) => string;
/**
* Custom sorting function for filtered results
* @param a - First option to compare
* @param b - Second option to compare
* @returns Comparison result (-1, 0, 1)
*/
filteringSortFunc?: (a: any, b: any) => number;
/** Limit number of displayed options (default: 1000) */
optionsLimit?: number;
}Usage Example:
<template>
<VueMultiselect
v-model="selectedProduct"
:options="products"
:searchable="true"
:custom-label="customProductLabel"
:filtering-sort-func="sortByRelevance"
:options-limit="50"
label="name"
track-by="id">
</VueMultiselect>
</template>
<script>
export default {
data() {
return {
selectedProduct: null,
products: [
{ id: 1, name: 'Laptop Pro', price: 1299, category: 'Electronics' },
{ id: 2, name: 'Office Chair', price: 299, category: 'Furniture' }
]
}
},
methods: {
customProductLabel({ name, price, category }) {
return `${name} - $${price} (${category})`;
},
sortByRelevance(a, b) {
// Custom sorting logic based on price, category, etc.
if (a.category !== b.category) {
return a.category.localeCompare(b.category);
}
return a.price - b.price;
}
}
}
</script>Events related to search functionality for implementing async or custom search.
/**
* Search-related events
*/
interface SearchEvents {
/**
* Emitted when search query changes
* @param searchQuery - Current search string
* @param id - Component identifier
*/
'@search-change': (searchQuery: string, id: string | number) => void;
}Implement asynchronous option loading based on search queries.
/**
* Async search configuration props
*/
interface AsyncSearchProps {
/** Disable internal filtering for async search (default: true) */
internalSearch: false;
/** Show loading spinner during async operations */
loading?: boolean;
/** Current options array (updated asynchronously) */
options: any[];
}Usage Example:
<template>
<VueMultiselect
v-model="selectedUser"
:options="searchResults"
:internal-search="false"
:loading="isLoading"
:searchable="true"
@search-change="searchUsers"
label="name"
track-by="id"
placeholder="Search users...">
</VueMultiselect>
</template>
<script>
export default {
data() {
return {
selectedUser: null,
searchResults: [],
isLoading: false
}
},
methods: {
async searchUsers(query) {
if (query.length < 2) {
this.searchResults = [];
return;
}
this.isLoading = true;
try {
const response = await fetch(`/api/users/search?q=${encodeURIComponent(query)}`);
this.searchResults = await response.json();
} catch (error) {
console.error('Search failed:', error);
this.searchResults = [];
} finally {
this.isLoading = false;
}
}
}
}
</script>Implement search with predefined suggestions or recent searches.
<template>
<VueMultiselect
v-model="selectedLocation"
:options="currentOptions"
:searchable="true"
@search-change="updateSearchOptions"
label="name"
track-by="id"
placeholder="Search locations...">
<template #noResult="{ search }">
<span>No locations found for "{{ search }}"</span>
</template>
<template #noOptions>
<span>Start typing to search locations</span>
</template>
</VueMultiselect>
</template>
<script>
export default {
data() {
return {
selectedLocation: null,
allLocations: [
{ id: 1, name: 'New York', country: 'USA' },
{ id: 2, name: 'London', country: 'UK' },
{ id: 3, name: 'Tokyo', country: 'Japan' }
],
recentSearches: [],
currentOptions: []
}
},
methods: {
updateSearchOptions(query) {
if (!query) {
// Show recent searches when no query
this.currentOptions = this.recentSearches;
} else {
// Filter based on query
this.currentOptions = this.allLocations.filter(location =>
location.name.toLowerCase().includes(query.toLowerCase()) ||
location.country.toLowerCase().includes(query.toLowerCase())
);
}
}
}
}
</script>Options for optimizing search performance with large datasets.
/**
* Performance optimization props
*/
interface SearchPerformanceProps {
/** Maximum number of options to display (default: 1000) */
optionsLimit?: number;
/** Custom sorting function for performance optimization */
filteringSortFunc?: (a: any, b: any) => number;
/** Disable internal search for better async performance */
internalSearch?: boolean;
}Usage Example:
<template>
<VueMultiselect
v-model="selectedItem"
:options="filteredItems"
:options-limit="100"
:internal-search="false"
:searchable="true"
@search-change="debounceSearch"
placeholder="Search in large dataset...">
</VueMultiselect>
</template>
<script>
export default {
data() {
return {
selectedItem: null,
allItems: [], // Large dataset
filteredItems: [],
searchTimeout: null
}
},
methods: {
debounceSearch(query) {
clearTimeout(this.searchTimeout);
this.searchTimeout = setTimeout(() => {
this.performSearch(query);
}, 300);
},
performSearch(query) {
if (!query) {
this.filteredItems = this.allItems.slice(0, 100);
return;
}
this.filteredItems = this.allItems
.filter(item => item.name.toLowerCase().includes(query.toLowerCase()))
.slice(0, 100);
}
}
}
</script>Customize the search input behavior and appearance.
/**
* Search input customization props
*/
interface SearchInputProps {
/** HTML name attribute for the search input */
name?: string;
/** Tab index for keyboard navigation */
tabindex?: number;
/** Enable/disable spellcheck */
spellcheck?: boolean;
/** Prevent automatic focus on component activation */
preventAutofocus?: boolean;
/** HTML required attribute when no selection made */
required?: boolean;
}Install with Tessl CLI
npx tessl i tessl/npm-vue-multiselect