Everything you wish the HTML select element could do, wrapped up into a lightweight, extensible Vue component.
—
Dynamic option creation functionality allowing users to create new options from search input, with customizable creation logic and tag management.
Properties that enable and control option creation from user input.
/**
* Enable/disable creating options from search input
* When true, allows users to create new options by typing
*/
taggable: Boolean // default: false
/**
* When true, newly created tags will be added to the options list
* Useful for persistent tag creation across sessions
*/
pushTags: Boolean // default: false
/**
* User-defined function for creating new options
* Determines format of created options
* @param option - The search text to convert to an option
* @returns Created option object or string
*/
createOption: Function // default: creates object with label key or returns stringData properties that track created tags and their state.
// Data properties
data: {
/**
* Array of tags that have been created when pushTags is enabled
*/
pushedTags: Array // default: []
}
// Computed properties
computed: {
/**
* Available options including pushed tags
* Combines original options with user-created tags
*/
optionList: Array
}Methods for creating, managing, and validating new tags.
/**
* Add a created tag to the pushed tags list
* Called automatically when pushTags is true
* @param option - The tag option to add
*/
pushTag(option: Object | String): void
/**
* Check if an option already exists in the option list
* Prevents duplicate tag creation
* @param option - Option to check for existence
* @returns Whether the option exists
*/
optionExists(option: Object | String): Boolean
/**
* Delete value on Delete keypress when no text in search input
* Handles tag removal via keyboard in multiple selection mode
* @returns Deleted value or undefined
*/
maybeDeleteValue(): anyEvents emitted during tag creation and management.
/**
* Emitted when a new tag/option is created from user input
* @param option - The newly created option
*/
'option:created': (option: Object | String) => void<template>
<v-select
v-model="selectedTags"
:options="availableTags"
taggable
multiple
placeholder="Type to create new tags..."
/>
</template>
<script>
export default {
data() {
return {
selectedTags: [],
availableTags: ['JavaScript', 'Vue.js', 'React', 'Angular']
};
}
};
</script><template>
<v-select
v-model="selectedCategories"
:options="categories"
taggable
pushTags
multiple
@option:created="onTagCreated"
placeholder="Add or create categories..."
/>
</template>
<script>
export default {
data() {
return {
selectedCategories: [],
categories: ['Electronics', 'Clothing', 'Books', 'Home & Garden']
};
},
methods: {
onTagCreated(newTag) {
console.log('New category created:', newTag);
// Could save to backend, analytics, etc.
}
}
};
</script><template>
<v-select
v-model="selectedUsers"
:options="users"
:createOption="createUserOption"
taggable
multiple
label="name"
placeholder="Add existing users or create new ones..."
/>
</template>
<script>
export default {
data() {
return {
selectedUsers: [],
users: [
{ id: 1, name: 'John Doe', email: 'john@example.com', type: 'existing' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', type: 'existing' }
]
};
},
methods: {
createUserOption(inputText) {
// Create a user object from input text
const email = inputText.includes('@') ? inputText : `${inputText}@example.com`;
return {
id: Date.now(), // Temporary ID
name: inputText,
email: email,
type: 'new'
};
}
}
};
</script><template>
<v-select
v-model="selectedSkills"
:options="skills"
:createOption="createSkillTag"
taggable
pushTags
multiple
@option:created="validateAndSaveSkill"
placeholder="Add skills (will be formatted)..."
/>
</template>
<script>
export default {
data() {
return {
selectedSkills: [],
skills: [
'JavaScript', 'Python', 'Java', 'C++', 'Go',
'React', 'Vue.js', 'Angular', 'Node.js'
]
};
},
methods: {
createSkillTag(skillText) {
// Format and validate skill name
const formattedSkill = skillText
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
return formattedSkill;
},
validateAndSaveSkill(skill) {
// Validate skill length
if (skill.length < 2) {
this.$nextTick(() => {
// Remove invalid skill
this.selectedSkills = this.selectedSkills.filter(s => s !== skill);
});
alert('Skill name must be at least 2 characters long');
return;
}
console.log('Valid skill created:', skill);
// Could save to backend
}
}
};
</script><template>
<v-select
v-model="selectedEmails"
:options="commonEmails"
:createOption="createEmailOption"
taggable
multiple
placeholder="Enter email addresses..."
/>
</template>
<script>
export default {
data() {
return {
selectedEmails: [],
commonEmails: [
'admin@example.com',
'support@example.com',
'info@example.com'
]
};
},
methods: {
createEmailOption(emailText) {
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (emailRegex.test(emailText)) {
return emailText.toLowerCase();
} else {
// Return invalid marker or throw error
return null; // This will prevent tag creation
}
}
}
};
</script><template>
<v-select
v-model="selectedTasks"
:options="existingTasks"
:createOption="createTaskOption"
taggable
pushTags
multiple
label="title"
:reduce="task => task.id"
placeholder="Add existing tasks or create new ones..."
/>
</template>
<script>
export default {
data() {
return {
selectedTasks: [],
existingTasks: [
{ id: 1, title: 'Review PR #123', priority: 'high', status: 'pending' },
{ id: 2, title: 'Update documentation', priority: 'medium', status: 'in-progress' }
]
};
},
methods: {
createTaskOption(taskTitle) {
// Create a task object with default values
return {
id: `temp-${Date.now()}`, // Temporary ID
title: taskTitle,
priority: 'medium',
status: 'pending',
createdAt: new Date().toISOString(),
createdBy: 'user'
};
}
}
};
</script><template>
<div>
<label>
<input type="checkbox" v-model="allowNewTags"> Allow creating new tags
</label>
<v-select
v-model="selectedTags"
:options="predefinedTags"
:taggable="allowNewTags"
multiple
:placeholder="allowNewTags ? 'Type to create tags...' : 'Choose from existing tags...'"
/>
</div>
</template>
<script>
export default {
data() {
return {
allowNewTags: false,
selectedTags: [],
predefinedTags: ['Tag 1', 'Tag 2', 'Tag 3', 'Tag 4']
};
}
};
</script><template>
<v-select
v-model="selectedTopics"
:options="topics"
:createOption="createTopicOption"
taggable
pushTags
multiple
:loading="isCreatingTopic"
@option:created="saveNewTopic"
placeholder="Add or create discussion topics..."
/>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
selectedTopics: [],
topics: [],
isCreatingTopic: false
};
},
async created() {
// Load existing topics
try {
const response = await axios.get('/api/topics');
this.topics = response.data;
} catch (error) {
console.error('Failed to load topics:', error);
}
},
methods: {
createTopicOption(topicName) {
return {
id: null, // Will be set by backend
name: topicName,
isNew: true
};
},
async saveNewTopic(topic) {
if (!topic.isNew) return;
this.isCreatingTopic = true;
try {
const response = await axios.post('/api/topics', {
name: topic.name
});
// Update the topic with real ID from backend
const savedTopic = response.data;
const index = this.topics.findIndex(t => t === topic);
if (index > -1) {
this.$set(this.topics, index, savedTopic);
}
// Update selection if this topic was selected
const selectedIndex = this.selectedTopics.findIndex(t => t === topic);
if (selectedIndex > -1) {
this.$set(this.selectedTopics, selectedIndex, savedTopic);
}
} catch (error) {
console.error('Failed to save new topic:', error);
// Remove the failed topic
this.topics = this.topics.filter(t => t !== topic);
this.selectedTopics = this.selectedTopics.filter(t => t !== topic);
} finally {
this.isCreatingTopic = false;
}
}
}
};
</script>Install with Tessl CLI
npx tessl i tessl/npm-vue-select