Search⌘ K
AI Features

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.

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:

HTML
<template>
<div>
<input v-model="myText">
<!-- is equivalent to (does the same as) -->
<input
@input="e => myText = e.target.value"
:value="myText"
>
</div>
</template>
<script>
// ...
</script>

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 input event and the value prop 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>

Implement a Vue app with a custom form component that should be two-way bound

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>

Implement the code in this Vue SPA that should implement a custom form component according to the requirements mentioned above

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.