Vue.js Select Options
Working with dropdown menus is a common requirement in web applications, from selecting a country on a registration form to filtering data in a dashboard. Vue.js provides a straightforward way to implement these controls through HTML's native <select>
element, while adding powerful data binding and event handling capabilities.
Introduction to Select Dropdowns in Vue
In Vue.js, select dropdowns are created using the standard HTML <select>
and <option>
tags, enhanced with Vue's directives for two-way data binding and reactivity. This allows us to easily connect our dropdown selections to our application's state.
Basic Select Implementation
Let's start with a simple select dropdown that lets users choose their favorite programming language:
<template>
<div>
<label for="language">Select your favorite language:</label>
<select id="language" v-model="selectedLanguage">
<option value="">Please select a language</option>
<option value="javascript">JavaScript</option>
<option value="python">Python</option>
<option value="ruby">Ruby</option>
<option value="java">Java</option>
<option value="csharp">C#</option>
</select>
<p>You selected: {{ selectedLanguage || 'Nothing yet' }}</p>
</div>
</template>
<script>
export default {
data() {
return {
selectedLanguage: ''
}
}
}
</script>
In this example:
- We use
v-model
to create a two-way binding between the select input and theselectedLanguage
data property - When a user selects an option,
selectedLanguage
will automatically update - The paragraph displays the current selection, with a fallback message if nothing is selected
Dynamic Select Options
In most real-world applications, your options will come from an API or another data source rather than being hardcoded in the template. Let's see how to dynamically generate options:
<template>
<div>
<label for="language">Select your favorite language:</label>
<select id="language" v-model="selectedLanguage">
<option value="">Please select a language</option>
<option v-for="language in languages" :key="language.id" :value="language.value">
{{ language.text }}
</option>
</select>
<p>You selected: {{ selectedLanguageText }}</p>
</div>
</template>
<script>
export default {
data() {
return {
selectedLanguage: '',
languages: [
{ id: 1, value: 'javascript', text: 'JavaScript' },
{ id: 2, value: 'python', text: 'Python' },
{ id: 3, value: 'ruby', text: 'Ruby' },
{ id: 4, value: 'java', text: 'Java' },
{ id: 5, value: 'csharp', text: 'C#' }
]
}
},
computed: {
selectedLanguageText() {
if (!this.selectedLanguage) return 'Nothing yet';
const language = this.languages.find(lang => lang.value === this.selectedLanguage);
return language ? language.text : 'Unknown language';
}
}
}
</script>
Here we're using:
v-for
to iterate over an array of language objects:key
to provide a unique identifier for each option (important for Vue's rendering optimization):value
to bind the option's value- A computed property to display the selected language's text rather than its value
Working with Object Values
Vue allows us to bind entire objects to select options, which can be useful when working with complex data:
<template>
<div>
<label for="user">Select a user:</label>
<select id="user" v-model="selectedUser">
<option :value="null">Please select a user</option>
<option v-for="user in users" :key="user.id" :value="user">
{{ user.name }}
</option>
</select>
<div v-if="selectedUser" class="user-details">
<h3>User Details</h3>
<p>Name: {{ selectedUser.name }}</p>
<p>Email: {{ selectedUser.email }}</p>
<p>Role: {{ selectedUser.role }}</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
selectedUser: null,
users: [
{ id: 1, name: 'Alice Smith', email: '[email protected]', role: 'Admin' },
{ id: 2, name: 'Bob Johnson', email: '[email protected]', role: 'Editor' },
{ id: 3, name: 'Carol Williams', email: '[email protected]', role: 'Viewer' }
]
}
}
}
</script>
<style scoped>
.user-details {
margin-top: 20px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #f9f9f9;
}
</style>
In this example:
- We bind the entire user object to each option using
:value="user"
- When a user is selected, we can access all of their properties through
selectedUser
- We display additional user details when a selection is made
Multiple Selection
HTML select elements can allow multiple selections by adding the multiple
attribute. Here's how to implement a multi-select dropdown in Vue:
<template>
<div>
<label for="skills">Select your skills (hold Ctrl/Cmd to select multiple):</label>
<select id="skills" v-model="selectedSkills" multiple>
<option v-for="skill in skills" :key="skill.id" :value="skill.value">
{{ skill.text }}
</option>
</select>
<div class="selected-skills">
<h3>Your skills:</h3>
<ul v-if="selectedSkills.length > 0">
<li v-for="(skill, index) in selectedSkills" :key="index">{{ getSkillText(skill) }}</li>
</ul>
<p v-else>No skills selected yet</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
selectedSkills: [],
skills: [
{ id: 1, value: 'html', text: 'HTML' },
{ id: 2, value: 'css', text: 'CSS' },
{ id: 3, value: 'javascript', text: 'JavaScript' },
{ id: 4, value: 'vue', text: 'Vue.js' },
{ id: 5, value: 'react', text: 'React' },
{ id: 6, value: 'node', text: 'Node.js' }
]
}
},
methods: {
getSkillText(value) {
const skill = this.skills.find(s => s.value === value);
return skill ? skill.text : value;
}
}
}
</script>
<style scoped>
select[multiple] {
height: 150px;
width: 200px;
padding: 5px;
}
.selected-skills {
margin-top: 20px;
}
</style>
Key points for multiple selection:
- Initialize
selectedSkills
as an array to store multiple values - Add the
multiple
attribute to the select element v-model
will automatically update the array when selections change- We added a helper method to display the text labels of selected skills
Customizing Select Behavior
Adding a Default Option
It's common to include a default "placeholder" option to prompt users to make a selection:
<select v-model="selected">
<option value="" disabled>Please select an option</option>
<option v-for="option in options" :key="option.id" :value="option.value">
{{ option.text }}
</option>
</select>
The disabled
attribute prevents users from selecting the placeholder, forcing them to choose a valid option.
Handling Selection Changes
You can respond to changes in selection using the @change
event:
<template>
<div>
<select v-model="selectedCategory" @change="loadSubcategories">
<option value="">Select a category</option>
<option v-for="category in categories" :key="category.id" :value="category.id">
{{ category.name }}
</option>
</select>
<select v-if="subcategories.length > 0" v-model="selectedSubcategory">
<option value="">Select a subcategory</option>
<option v-for="subcategory in subcategories" :key="subcategory.id" :value="subcategory.id">
{{ subcategory.name }}
</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
selectedCategory: '',
selectedSubcategory: '',
categories: [
{ id: 1, name: 'Electronics' },
{ id: 2, name: 'Clothing' },
{ id: 3, name: 'Books' }
],
subcategories: [],
allSubcategories: {
1: [
{ id: 101, name: 'Smartphones' },
{ id: 102, name: 'Laptops' },
{ id: 103, name: 'Accessories' }
],
2: [
{ id: 201, name: 'Men\'s' },
{ id: 202, name: 'Women\'s' },
{ id: 203, name: 'Children\'s' }
],
3: [
{ id: 301, name: 'Fiction' },
{ id: 302, name: 'Non-fiction' },
{ id: 303, name: 'Educational' }
]
}
}
},
methods: {
loadSubcategories() {
this.selectedSubcategory = ''; // Reset subcategory selection
if (this.selectedCategory) {
this.subcategories = this.allSubcategories[this.selectedCategory] || [];
} else {
this.subcategories = [];
}
}
}
}
</script>
This example demonstrates a common pattern where selecting a category populates a second dropdown with related subcategories.
Styling Select Dropdowns
While HTML select elements can be difficult to fully customize, you can apply basic styling:
<template>
<div>
<select v-model="selected" class="styled-select">
<option value="">Select an option</option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
selected: ''
}
}
}
</script>
<style scoped>
.styled-select {
display: block;
width: 100%;
padding: 10px;
font-size: 16px;
font-family: inherit;
color: #333;
background-color: white;
border: 1px solid #ddd;
border-radius: 4px;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23333' d='M6 8.825L1.175 4 2.238 2.938 6 6.7 9.763 2.937 10.825 4z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 10px center;
padding-right: 30px;
}
.styled-select:focus {
outline: none;
border-color: #80bdff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
</style>
This styling includes:
- Consistent sizing and padding
- Custom dropdown arrow using CSS
background-image
- Improved focus styles for accessibility
- Removal of default browser styling with
appearance: none
For more advanced custom dropdowns, consider using Vue components like vue-select
or vue-multiselect
.
Practical Example: Country-State Selection Form
Here's a real-world example of a form that lets users select their country and corresponding state/province:
<template>
<div class="location-form">
<h2>Location Information</h2>
<div class="form-group">
<label for="country">Country:</label>
<select id="country" v-model="selectedCountry" @change="onCountryChange" class="form-control">
<option value="">Select a country</option>
<option v-for="country in countries" :key="country.code" :value="country.code">
{{ country.name }}
</option>
</select>
</div>
<div class="form-group" v-if="states.length > 0">
<label for="state">State/Province:</label>
<select id="state" v-model="selectedState" class="form-control">
<option value="">Select a state/province</option>
<option v-for="state in states" :key="state.code" :value="state.code">
{{ state.name }}
</option>
</select>
</div>
<div class="form-group" v-else-if="selectedCountry">
<label for="region">Region/City:</label>
<input id="region" v-model="region" type="text" class="form-control" placeholder="Enter your region or city">
</div>
<div class="summary" v-if="selectedCountry">
<h3>Location Summary</h3>
<p>
Country: {{ getCountryName(selectedCountry) }}<br>
{{ states.length > 0 ? 'State/Province: ' + getStateName(selectedState) : 'Region: ' + region }}
</p>
</div>
<button
class="submit-btn"
:disabled="!isFormValid"
@click="submitForm"
>
Submit
</button>
</div>
</template>
<script>
export default {
data() {
return {
selectedCountry: '',
selectedState: '',
region: '',
countries: [
{ code: 'us', name: 'United States' },
{ code: 'ca', name: 'Canada' },
{ code: 'mx', name: 'Mexico' },
{ code: 'uk', name: 'United Kingdom' },
{ code: 'fr', name: 'France' }
],
statesByCountry: {
us: [
{ code: 'ny', name: 'New York' },
{ code: 'ca', name: 'California' },
{ code: 'tx', name: 'Texas' },
{ code: 'fl', name: 'Florida' }
],
ca: [
{ code: 'on', name: 'Ontario' },
{ code: 'qc', name: 'Quebec' },
{ code: 'bc', name: 'British Columbia' }
]
},
states: []
}
},
computed: {
isFormValid() {
if (!this.selectedCountry) return false;
if (this.states.length > 0) {
return !!this.selectedState;
}
return !!this.region;
}
},
methods: {
onCountryChange() {
this.selectedState = '';
this.region = '';
this.states = this.statesByCountry[this.selectedCountry] || [];
},
getCountryName(code) {
const country = this.countries.find(c => c.code === code);
return country ? country.name : '';
},
getStateName(code) {
if (!code) return '';
const state = this.states.find(s => s.code === code);
return state ? state.name : '';
},
submitForm() {
// In a real application, you would send this data to your server
console.log({
country: this.selectedCountry,
countryName: this.getCountryName(this.selectedCountry),
state: this.selectedState,
stateName: this.getStateName(this.selectedState),
region: this.region
});
alert('Form submitted successfully!');
}
}
}
</script>
<style scoped>
.location-form {
max-width: 500px;
margin: 0 auto;
padding: 20px;
background: #f9f9f9;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-control {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
.summary {
margin: 20px 0;
padding: 15px;
background: #e9f7ef;
border-radius: 4px;
}
.submit-btn {
padding: 10px 20px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.submit-btn:disabled {
background: #cccccc;
cursor: not-allowed;
}
.submit-btn:not(:disabled):hover {
background: #45a049;
}
</style>
This example demonstrates several important concepts:
- Dependent dropdowns (state selection depends on country)
- Conditional form elements (showing a text input when states aren't available)
- Form validation using computed properties
- Displaying a summary of selections
- Styled form elements with a disabled submit button until the form is valid
Summary
Vue's integration with HTML select elements provides a powerful way to create interactive and dynamic dropdowns in your forms. We've covered:
- Basic select implementation with
v-model
- Dynamically generating options with
v-for
- Working with object values
- Multi-select functionality
- Handling selection changes
- Styling select elements
- Real-world application in a country/state form
By leveraging Vue's reactivity system and directives, you can create form controls that are both user-friendly and connected seamlessly to your application's data.
Further Learning
To expand your knowledge of Vue.js form handling, consider these exercises:
- Filter Component: Create a product listing with a category filter dropdown that shows only products in the selected category
- Search with Autocomplete: Build a select dropdown that populates options based on a user's search input
- Multi-level Navigation: Create a navigation system with cascading dropdowns (category → subcategory → item)
- Custom Select Component: Try building your own reusable select component that supports searching, custom styling, and keyboard navigation
Remember, the HTML select element has limitations in terms of styling and mobile experience. For more advanced use cases, consider third-party libraries like Vue Select (https://vue-select.org/) or exploring custom dropdown implementations.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)