Building a “Select All” Checkbox Composable in Vue
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 beindeterminate
(a visual cue indicating partial selection). Clicking it deselects all items. - All items selected → The
Select All
checkbox should bechecked
. 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 all
checkbox 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! 🎉