Using `v-model` to Create a Custom Form Control
Explore how to use Vue's v-model directive to create custom form controls that enable two-way data binding. Learn techniques to bind parent data to custom components, manage user input effectively, and build interactive form elements that can be reused throughout your application.
We'll cover the following...
Custom form controls or, more precisely, custom input elements are a staple of innovative and modern web applications. There are countless possibilities for custom inputs—the only boundary is our creativity. Thanks to Vue’s implementation of two-way binding, we can turn almost any interaction into a ready-to-use form element.
Two-way binding repetition and further understanding
Let’s do a quick repetition of how Vue generates two-way binding out of v-model and how we can rebuild v-model. We can two-way bind any data or computed prop to an input field using v-model. The v-model is equivalent to using an event listener for the input event and setting the value property of the same field. In code, equivalence means the following:
Conversely, we know that any component that uses a prop called modelValue and emits an update:modelValue event can be two-way bound via v-model. Let’s try this.
Note: Vue 2 uses the
inputevent and thevalueprop instead.
The below SPA has a simple component with three buttons. Each button sets a local variable to a different value. Try to two-way bind it in the app by emitting an update:modelValue event and setting the value via a modelValue prop.
<template>
<div>
<p>
Selected: {{ selected }}
</p>
<ul>
<li>
<button @click="selected = 'First'">
Set value to "First"
</button>
</li>
<li>
<button @click="selected = 'Second'">
Set value to "Second"
</button>
</li>
<li>
<button @click="selected = 'Third'">
Set value to "Third"
</button>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
selected: 'None',
}
},
}
</script>
For a possible solution, open the hint below:
Requirements for our custom form control
Let’s now define what our more sophisticated custom control should do. Think of a wall with sticky notes on it. It’s divided into Available and Selected sections. A user should be able to add new items to the Available list and select them by clicking on them. The selected items should be a list of strings bound to a data prop on the parent via v-model. Below is a wireframe of the custom form element.
We prepared an SPA with all the necessary components in place. The component SelectList.vue should implement the input field to add new Available cards and be two-way bindable by the parent. The parent app should render the list as an unnumbered list.
<template>
<div class="container">
<div>
<h2>Available</h2>
<div v-for="(item, key) in available" class="card" :key="key">
{{ item }}
</div>
</div>
<div>
<h2>Selected</h2>
<div v-for="(item, key) in selected" class="card" :key="key">
{{ item }}
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
available: [
'Item A',
'Item B',
'Item C',
],
selected: [
'Item D'
],
}
}
}
</script>
<style>
.card {
width: 100px;
height: 100px;
border: 2px solid #000;
margin-bottom: 20px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.container {
width: 500px;
display: flex;
}
.container > div {
padding: 20px;
}
.container > :first-child {
border-right: 1px solid #000;
}
</style>
See the hint below for a possible solution:
We can use these techniques to turn almost any user input into a custom form element. For example, we can turn entire modals with search fields and filtering into a single form element that is bindable via v-model. The v-model allows us to reuse these components without a complex interface.