Skip to main content

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:

html
<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 the selectedLanguage 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:

html
<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:

html
<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:

html
<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:

html
<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:

html
<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:

html
<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:

html
<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:

  1. Dependent dropdowns (state selection depends on country)
  2. Conditional form elements (showing a text input when states aren't available)
  3. Form validation using computed properties
  4. Displaying a summary of selections
  5. 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:

  1. Filter Component: Create a product listing with a category filter dropdown that shows only products in the selected category
  2. Search with Autocomplete: Build a select dropdown that populates options based on a user's search input
  3. Multi-level Navigation: Create a navigation system with cascading dropdowns (category → subcategory → item)
  4. 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! :)