Building a “Select All” Checkbox Composable in Vue

Marco Antonio Arruda
5 min readFeb 18, 2025

--

Managing checkboxes in a Vue 3 application can quickly become tricky, especially when dealing with “Select All” functionality. Whether you’re building a multi-select form, a bulk action feature, or a complex data table, ensuring that checkboxes behave correctly is crucial for a smooth user experience.

In this post, we’ll walk through creating a reusable Vue 3 composable to handle “Select All” functionality efficiently. By leveraging the Composition API, we’ll write clean, modular, and reusable code that can be easily integrated into different parts of your application. Let’s dive in! 🚀

Designing the behavior of SelectAll checkbox

💭 If you don’t know the Vue way of managing list of values with multiple checkboxes, please check this post! 📚

Before jumping into the implementation, it’s important to define the expected behavior of the Select All checkbox. It should react dynamically based on the selection state of the list and provide an intuitive experience for users.

✅ Expected Behavior

  • No items selected → The Select All checkbox should be unchecked. Clicking it selects all items.
  • Some items selected → The Select All checkbox should be indeterminate (a visual cue indicating partial selection). Clicking it deselects all items.
  • All items selected → The Select All checkbox should be checked. Clicking it deselects everything.

By defining these rules, we ensure the checkbox behaves consistently, improving usability and avoiding confusion.

Initial template

Let’s start with a simple example to illustrate how multiple checkboxes are managed in Vue. We’ll create a list of options and a Select All checkbox.

Setup the Component

First, define the items and selected items as reactive states:

<script lang="ts" setup>
import { ref } from 'vue'

const items = ref<Array<string>>(['Option 1', 'Option 2', 'Option 3'])
const selectedItems = ref<string[]>([])

// select all checkbox state
const checked = ref(false)
const indeterminate = ref(false)
</script>

Template Structure

Now, add the checkboxes in the template:

<template>
<div class="flex flex-col gap-2 p-5">
<h1>Multiple checkboxes with an Array</h1>
<div>
<input type="checkbox" v-model="checked" :indeterminate="indeterminate" />
<label>Select all</label>
</div>
<hr />
<div v-for="(option, index) of items" :key="index">
<input type="checkbox" :value="option" v-model="selectedItems" />
<label>{{ option }}</label>
</div>

<div>Selected items: {{ selectedItems }}</div>
</div>
</template>

🐛 You can test it here

At this stage, the Select All checkbox is not yet wired to the list of checkboxes — it doesn’t control them, nor does it reflect their state. A common approach would be to listen for a click event and manually update the selection, but we’ll take a cleaner approach using a writable computed property.

In the next step, we’ll extract the logic into a composable to keep our code modular and reusable.

💭 If you haven’t heard about it before, you can read on this post! 📚

Extracting the Select All logic to a composable

The title of the post promised a composable, so let’s start by moving both state variables of the Select allcheckbox to a new composable named useCheckboxSelectAll. It will receive both items and selectedItems variables as parameters:

import { type Ref, ref } from 'vue'

export const useCheckboxSelectAll = <T>(
items: Ref<T[]>,
selected: Ref<T[]>
) => {
const checked = ref<boolean>()
const indeterminate = ref<boolean>()

return {
checked,
indeterminate
}
}

And replace the ref's we created in the main file by:

// select all checkbox state
const { checked, indeterminate } = useCheckboxSelectAll(items, selectedItems)

First of all, let’s make the Select all checkbox to react to the list status. After that, let’s do the opposite… by clicking on the Select all checkbox, we need to either select or deselect all the rest!

Make SelectAll Checkbox Reactive to the list

At this point, we must re-write the states by using computed Vue function and write the conditions we agreed before. These can be translated as:

import { type Ref, computed } from 'vue'

export const useCheckboxSelectAll = <T>(
items: Ref<T[]>,
selected: Ref<T[]>
) => {
const checked = computed(() => {
return selected.value.length > 0 &&
selected.value.length === items.value.length
})
const indeterminate = computed<boolean>(() => {
return selected.value.length > 0 &&
selected.value.length < items.value.length
})

return {
checked,
indeterminate
}
}

🐛 You can test it here

If you try selecting the items at this point, you’ll see the desired behavior:

But… if you click on the Select all checkbox, you’ll get an error, once we didn’t implement the setter of it yet!!

Make the Select All Checkbox Writable

At this moment, we must rewrite the computed checked state to support get and set methods. This is the correct syntax, from official Vue docs. In our case, we want to perform some changes on the ref parameters whenever we set the checkbox value:

const checked = computed({
get() {
return selected.value.length > 0 &&
selected.value.length === items.value.length
},
set(value) {
if (value) selected.value = items.value
else selected.value = []
}
})

And the checkbox should now work when we try to select or deselect all!

There’s one little issue you may have noticed, though! When its state is indeterminate , we want the checkbox to Deselect all when clicked! And it’s performing just the opposite!

So what we should do is: return true for checked attribute when it is indeterminate , which will lead the checkbox to beunckeched when clicked! We can easily add this behavior by adding a new condition with an early return in the checked get attribute:

get() {
if (indeterminate.value) return true

return selected.value.length > 0 &&
selected.value.length === items.value.length
}

And we’ll get the final desired behavior:

🐛 You can test and get the final code here 🐛

Conclusion 🚀

By using Vue’s Composition API, we created a reusable useCheckboxSelectAll composable that keeps our checkbox logic clean and efficient. This approach makes it easy to manage "Select All" functionality in different parts of your app without duplicating code.

This technique can be extended further! You might:

  • Use it with different data sources (e.g., objects, async data).
  • Add optional callbacks for when selection changes.
  • Improve accessibility with proper ARIA attributes.

Check out the final working example here! Let me know if you have any questions or suggestions. Happy coding! 🎉

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Marco Antonio Arruda
Marco Antonio Arruda

No responses yet

Write a response