This tutorial aims to help you understand how v-model works on native inputs and on custom components by default. Also, you’ll learn how to create custom checkboxes and radios that emulate how v-model works on them natively.

In particular, form inputs tend to have plenty of complexity that you’d want to hide in a component, such as custom designs, labels, validation, help messages, and making sure each of these pieces are in the correct order so that they render correctly.

Writing Reusable Modules in ES6

Are you excited to take advantage of new JavaScript language features but not sure where to start, or how? Read a related article →

On top of that though, Vue has a built-in directive called v-model that simulates 2-way binding by binding a value and capturing input events. If you’re going to build a custom input component, then you’ll definitely want to support the v-model directive.

Sadly, when I looked around for examples of custom inputs in Vue for radio buttons or checkboxes, they either didn’t take v-model into consideration at all, or they failed to implement it correctly. There is some decent documentation for custom text inputs, but since it doesn’t explain customizing radios or checkboxes, we’ll discuss that here.

When using a text input (including types such as email, number, etc.) or textarea, v-model="varName" is equivalent to :value="varName" @input="e => varName = e.target.value". This means that the value of the input is set to varName after each update to the input varName is updated to the value of the input. A normal select element will act like this too, though a multiple select will be different.

Note how v-model isn’t even touching value anymore. It’s still doing the same thing in the change event handler (though it was changed to change instead of input), but now it’s determining whether checked should be true or false depending on whether picked is the same as the value of that radio button.

Checkboxes

Checkboxes are a bit more difficult to talk about because they have two different behaviors depending on whether there is only a single checkbox with a given v-model or multiple.

If you are using a single checkbox, v-model will treat it like a boolean and ignore the value.

That’s pretty much it for single-checkbox examples. If you have multiple checkboxes that share a model, then those checkboxes will fill an array with values of all the checkboxes that are checked, but make sure the model that you pass in is already an array or you’ll get some odd behavior. Also, the true-value and false-value attributes no longer affect anything.

That’s a lot more complicated than what we’ve seen before, but if you break it down, it’s not too bad. shouldBeChecked is true when that checkbox’s value is included in the array and false if it isn’t. updateVals adds the checkbox’s value to the array when it gets checked and removes it when it gets unchecked.

How Does v-model Work On Components?

Since Vue doesn’t know how your component is supposed to work, or if it’s trying to act as a replacement for a certain type of input, it treats all components the same with regards to v-model. It actually works the exact same way as it does for text inputs, except that in the event handler, it doesn’t expect an event object to be passed to it, rather it expects the value to be passed straight to it. So…

v-model will look at these properties and instead of using the value attribute, it’ll use the attribute you specify in prop and instead of listening for the input event, it’ll use the event you specified in event. So the above my-custom-component example would actually expand out to the following:

This is nice, but if we’re making a custom radio or checkbox, this doesn’t work very well. With some work, though, we can move the logic that v-model uses on radios and checkboxes inside our custom components.

Supporting v-model On Custom Radios

Compared to a checkbox, custom radios are quite simple. Here’s a very basic custom radio that I build that just wraps the input in a label and accepts a label property to add the label text.

Note: I only included props that are helpful for explaining how these should work with v-model, but input tags can take advantage of several other attributes (such as name or disabled), so make sure you create all of the props you’ll need and pass them on to input. You’ll also want to consider accessibility by adding WAI-ARIA attributes, as well as using slots for adding content rather than props like I did here with label.

You may think that since I didn’t include name in this example, a group of radios wouldn’t actually sync up with one another. Actually the updating of the model will in turn update the other radio buttons that share that model, so they don’t need to share a name like they do in plain HTML forms as long as they share the same model.

Supporting v-model On Custom Checkboxes

Making custom checkboxes is noticeably more complex than the radio buttons, primarily because we have to support two different use cases: a single true/false checkbox (that may or may not use true-value and/or false-value) and multiple checkboxes that combine all the checked values into an array.

So how do we determine which use case it is? You might think that we need to determine if there are other checkboxes with the same name attribute, but that’s not actually what Vue’s built-in system uses. Just like the radios, Vue doesn’t take the name attribute into consideration at all. That’s only used when natively submitting a form. So then you might think it determines it based on whether there are other checkboxes that share the same model, but that’s not it either. It’s determined by whether or not the model is an array. That’s it.

So the code will be structured similarly to the custom radio button’s code, but inside shouldBeChecked and updateInput the logic will split depending on whether or not modelValue is an array.

And there you have it. It may be better, though, to split this into two different components: one for handling the single true/false toggle and one for use in lists of options. That would allow it to more closely follow the Single-Responsibility Principle, but if you’re looking for a drop-in replacement to checkboxes, this is what you’re looking for (plus the addition of all the other useful attributes and custom features you might want).

Further Reading

There’s plenty more to learn about Custom Inputs, Vue Components, and Vue in general. I recommend giving some of these resources a look-through.

Awesome-Vue’s Component Sets
Awesome-Vue is a huge list of Vue-related projects and resources, so feel free to peruse anything and everything on that list, but in particular I want to point out the UI Libraries and Component Sets because they pretty much all have examples of Checkboxes and Radios you can look at if you feel like diving into their source code.

Vue Curated
This is a list similar to Awesome-Vue, but is more strictly curated so you know that everything on the list is worth taking a look at.

Vue Component Guide
The official Vue guide is a great place to learn the basics about anything related to Vue.