Build a custom multi-select checkbox component bound to an array using Vue 3, Tailwind Css, and Font Awesome
In response to a comment on Build a checkbox component with Vue 3, Font Awesome, & Tailwind CSS
Code Sandbox:
If you want to follow along, open up the Single Checkbox sandbox.
- Single Checkbox 👈 Where the last article left off
- Multiple Checkboxes 👈 Final code
We’ll be taking the single checkbox component we built and utilizing it in a new multi-checkbox component.
Step 1
Create a new component and import it into App.vue
Name the component multi-checkbox.vue
— it can be empty for now 😶
<template></template>
<script></script>
In App.vue
import the multi-checkbox
component, register it, and add it to your template in place of <checkbox …/>
from the last article.
Step 2 • Prepare the data
Add data and options in App.vue
For our multi-checkbox component to be reusable, it should receive its options and value as props. So let’s define our options and value in App.vue
so we can pass them down to our component.
To make it a little more interesting — let’s pretend we’re building an app that lets you select heroes that you’d like on your team.
- Add a const called
heroes
and set its value as an empty Array. This is what we’ll tie thev-model
to. - The options should be an Array of Objects. Each option should have an id and a name for display purposes. The id is what we will populate our
heroes
array with as the user checks each box. (Hope you’ve watched The Umbrella Academy)
Here is how your App.vue
script should look at this point.
<script>
import MultiCheckbox from "./components/multi-checkbox.vue";
import { ref } from "vue";setup() {
const heroes = ref([]);
const options = ref([
{ name: "Luther", id: 1 },
{ name: "Diego", id: 2 },
{ name: "Allison", id: 3 },
{ name: "Klaus", id: 4 },
{ name: "Five", id: 5 },
{ name: "Ben", id: 6 },
{ name: "Vanya", id: 7 },
]);return {
heroes,
options,
};
},
components: {
"multi-check-box": MultiCheckbox,
},
</script>
Tip: Read-up on reactivity https://vuejs.org/api/reactivity-core.html
Step 3 • Pass the data with props
One more thing in App.vue
– in the template, pass options
and heroes
to <multi-check-box/>
as props. Add v-model:
right before the value prop to utilize form input binding.
<multi-check-box v-model:value=”heroes” :options=”options” />
Ok, lets get back to the empty multi-checkbox.vue
component we made in step 1, we still need to add prop definitions to accept value
and options
Add the prop definitions
Open up multi-checkbox.vue
and add required props for options
and value
with type set as Array for each.
Bonus points: Ensure each option has a name
and an id
by writing a validator. 🧐
<script>
export default {
props: {
value: {
type: Array,
required: true,
},
options: {
type: Array,
required: true,
validator: (value) => {
const hasNameKey = value.every((option) =>
Object.keys(option).includes("name")
);
const hasIdKey = value.every((option) =>
Object.keys(option).includes("id")
);
return hasNameKey && hasIdKey;
},
},
},
</script>
More on .every(), Object.keys(), and .includes()
Step 4 • Render each option
Import and register the checkbox component as check-box
Throw in the checkbox with a v-for="option in options"
I wrapped the list of checkboxes in a div with a few tailwind classes to add some style.
<template>
<div class="flex flex-col items-start justify-center w-64 border-2 border-gray-400 p-8 rounded-lg bg-gray-100">
<check-box
v-for="option in options"
:fieldId="option.name"
:label="option.name"
:key="option"/>
</div>
</template>
Step 5 • Display options as checked
An option should appear as checked if the option’s id is included in the Array of ids (the value prop). Add the checked
prop to the <check-box …>
<check-box
v-for="option in options"
:checked="value.includes(option.id)"
:fieldId="option.name"
:label="option.name"
:key="option"/>
To test if this worked, jump over to App.vue
and update the heroes
Array to
const heroes = ref([1,3,5]);
If it worked then Luther, Allison and Five should be checked 👍
Step 6 • Update the value when a checkbox is clicked
In the single checkbox we emit checked
— but since these checkboxes are bound to an Array, we need to a little more. 💪
- Add/remove the clicked option id from the list of ids (the value)
- Then emit the updated list (to be caught by v-model on
App.vue
)
Add the setup method to the multi-checkbox component, and add the check method below.
setup(props, context) {
const check = (optionId, checked) => { // copy the value Array to avoid mutating props
let updatedValue = [...props.value]; // remove name if checked, else add name
if (checked) {
updatedValue.push(optionId);
} else {
updatedValue.splice(updatedValue.indexOf(optionId), 1);
} // emit the updated value
context.emit("update:value", updatedValue);
}; return {
check,
};
},
Then on each checkbox, listen for the @update
event and call the check
method. Be sure to pass the option.id
and the $event
(the $event
is the checked boolean passed from the check-box
)
<check-box
v-for="option in options"
:fieldId="option.name"
:checked="value.includes(option.id)"
:label="option.name"
@update:checked="check(option.id, $event)"
:key="option"
/>
Step 7 • Api interaction
So let’s say you’re working with existing data that you’d like to edit. If our hero selection app was real, we’d be interacting with an api that we would save our hero selection.
Api interaction is out of scope of this article, so we wont get too far into it.
In App.vue
you’d fetch data during the onMounted()
lifecycle hook, then update heroes
to whatever the api returned.
Here’s the basic idea assuming you use axios:
<script>
import MultiCheckbox from "./components/multi-checkbox.vue";
import { ref, onMounted } from "vue";
export default {
name: "App",
setup() {
let heroes = ref([]);
const options = ref([
{ name: "Luther", id: 1 },
{ name: "Diego", id: 2 },
{ name: "Allison", id: 3 },
{ name: "Klaus", id: 4 },
{ name: "Five", id: 5 },
{ name: "Ben", id: 6 },
{ name: "Vanya", id: 7 },
]); const getMyHeroes = async () => {
await axios.get('/heroes/').then(response =>
heroes.value = response.data.heroes
)
} onMounted(() => {
getMyHeroes()
}) return {
heroes,
options,
};
},
components: {
"multi-check-box": MultiCheckbox,
},
};
</script>
In this situation you would also want to be hitting an endpoint to receive the options as well. And of course, you’d have a method that would post/put/patch the selected heroes to some endpoint. But i’ll let you handle all that 😉
This should cover the main changes — everything is in that codesandbox
Thanks for the great question Jessie Green