Everything you wish the HTML select element could do, wrapped up into a lightweight, extensible Vue component.
—
Built-in search functionality with customizable filtering logic, supporting real-time option filtering and custom match algorithms.
Properties that control search input behavior and appearance.
/**
* Enable/disable search input functionality
*/
searchable: Boolean // default: true
/**
* Clear search text when an option is selected
*/
clearSearchOnSelect: Boolean // default: true
/**
* Function to determine if search should clear on blur
* Returns true when single selection and clearSearchOnSelect is true
*/
clearSearchOnBlur: Function
/**
* Query selector for finding search input in custom search slot
*/
searchInputQuerySelector: String // default: '[type=search]'Advanced filtering configuration for customizing how options are matched against search text.
/**
* Enable/disable filtering options by search text
* Should not be used with taggable
*/
filterable: Boolean // default: true
/**
* Custom filter matching logic for individual options
* @param option - The option to test
* @param label - The option's label text
* @param search - Current search text
* @returns Whether option matches search
*/
filterBy: Function // default: case-insensitive indexOf
/**
* Complete filter implementation for option lists
* @param options - Array of all options
* @param search - Current search text
* @returns Filtered array of options
*/
filter: Function // default: loops through options using filterByData properties and computed values that track search state.
// Data properties
data: {
/**
* Current search input value
*/
search: String, // default: ''
/**
* Whether user is currently composing text (IME input)
*/
isComposing: Boolean // default: false
}
// Computed properties
computed: {
/**
* Whether search input has a non-empty value
*/
searching: Boolean,
/**
* Computed placeholder text for search input
*/
searchPlaceholder: String | undefined,
/**
* Options filtered by search text and including taggable options
*/
filteredOptions: Array,
/**
* Reference to the search input DOM element
*/
searchEl: HTMLInputElement
}Events related to search input interactions.
/**
* Emitted when search input gains focus
*/
'search:focus': () => void
/**
* Emitted when search input loses focus
*/
'search:blur': () => void
/**
* Emitted when search text changes (from ajax mixin)
* Provides search text and loading toggle function for AJAX
* @param searchText - Current search input value
* @param toggleLoading - Function to toggle loading state
*/
'search': (searchText: String, toggleLoading: Function) => voidMethods for managing search input state and focus.
/**
* Handle search input focus - opens dropdown
*/
onSearchFocus(): void
/**
* Handle search input blur - closes dropdown
*/
onSearchBlur(): void
/**
* Handle search input keydown events
* @param e - Keyboard event
* @returns Keydown handler function
*/
onSearchKeyDown(e: KeyboardEvent): Function
/**
* Handle search input keypress events
* @param e - Keyboard event
*/
onSearchKeyPress(e: KeyboardEvent): void<template>
<v-select
v-model="selected"
:options="options"
searchable
placeholder="Search options..."
/>
</template>
<script>
export default {
data() {
return {
selected: null,
options: [
'Apple', 'Banana', 'Cherry', 'Date', 'Elderberry',
'Fig', 'Grape', 'Honeydew', 'Kiwi', 'Lemon'
]
};
}
};
</script><template>
<v-select
v-model="selected"
:options="products"
:filterBy="customFilter"
label="name"
placeholder="Search products..."
/>
</template>
<script>
export default {
data() {
return {
selected: null,
products: [
{ id: 1, name: 'MacBook Pro', category: 'Electronics', price: 1999 },
{ id: 2, name: 'iPhone 13', category: 'Electronics', price: 799 },
{ id: 3, name: 'Nike Air Max', category: 'Shoes', price: 120 }
]
};
},
methods: {
customFilter(option, label, search) {
// Search in name, category, and price
const searchLower = search.toLowerCase();
return (
option.name.toLowerCase().includes(searchLower) ||
option.category.toLowerCase().includes(searchLower) ||
option.price.toString().includes(search)
);
}
}
};
</script><template>
<v-select
v-model="selected"
:options="users"
:filter="customFilterImplementation"
label="displayName"
placeholder="Advanced user search..."
/>
</template>
<script>
export default {
data() {
return {
selected: null,
users: [
{ id: 1, firstName: 'John', lastName: 'Doe', email: 'john@example.com' },
{ id: 2, firstName: 'Jane', lastName: 'Smith', email: 'jane@example.com' },
{ id: 3, firstName: 'Bob', lastName: 'Johnson', email: 'bob@example.com' }
]
};
},
computed: {
usersWithDisplayName() {
return this.users.map(user => ({
...user,
displayName: `${user.firstName} ${user.lastName}`
}));
}
},
methods: {
customFilterImplementation(options, search) {
if (!search) return options;
const searchTerms = search.toLowerCase().split(' ');
return options.filter(user => {
const searchableText = `${user.firstName} ${user.lastName} ${user.email}`.toLowerCase();
return searchTerms.every(term => searchableText.includes(term));
});
}
}
};
</script><template>
<v-select
v-model="selected"
:options="searchResults"
:loading="isLoading"
@search="onSearch"
label="title"
placeholder="Search GitHub repositories..."
/>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
selected: null,
searchResults: [],
isLoading: false
};
},
methods: {
async onSearch(search, toggleLoading) {
if (search.length < 2) {
this.searchResults = [];
return;
}
toggleLoading(true);
try {
const response = await axios.get('https://api.github.com/search/repositories', {
params: { q: search, sort: 'stars', order: 'desc' }
});
this.searchResults = response.data.items.slice(0, 10);
} catch (error) {
console.error('Search error:', error);
this.searchResults = [];
} finally {
toggleLoading(false);
}
}
}
};
</script><template>
<div>
<v-select
v-model="selected"
:options="options"
:clearSearchOnSelect="false"
ref="vSelect"
placeholder="Search with persistent text..."
/>
<button @click="clearSearch">Clear Search</button>
<button @click="focusSearch">Focus Search</button>
<p>Current search: "{{ currentSearch }}"</p>
</div>
</template>
<script>
export default {
data() {
return {
selected: null,
options: ['Apple', 'Banana', 'Cherry', 'Date', 'Elderberry'],
currentSearch: ''
};
},
watch: {
'$refs.vSelect.search'(newSearch) {
this.currentSearch = newSearch;
}
},
methods: {
clearSearch() {
this.$refs.vSelect.search = '';
},
focusSearch() {
this.$refs.vSelect.searchEl.focus();
}
}
};
</script><template>
<v-select
v-model="selected"
:options="allOptions"
:filterable="false"
searchable
@search="onSearch"
placeholder="Search triggers external action..."
/>
</template>
<script>
export default {
data() {
return {
selected: null,
allOptions: ['Static Option 1', 'Static Option 2', 'Static Option 3']
};
},
methods: {
onSearch(searchText) {
// Handle search without filtering options
// Useful for AJAX search where server handles filtering
console.log('Search triggered:', searchText);
// Could trigger external API call, analytics, etc.
}
}
};
</script>Install with Tessl CLI
npx tessl i tessl/npm-vue-select