In Vue 3, how does v-model on a checkbox behave when bound to a boolean versus an array, and what does the value attribute do?
answer
- the bound value picks the mode
- single box vs a group
- value attribute names the entry
- new array on every change
basics
~20 sBound to a boolean, a checkbox's v-model stores true or false. Bound to an array or Set, each checked box adds its own value attribute to the collection and unchecking removes it, so every box in the group needs a distinct value.
solid answer
~40 sVue picks the checkbox mode from the **current type of the bound value**, not from the markup. With a boolean (or anything that is not an array or Set) the box writes `true`/`false` — or the `true-value`/`false-value` pair if you set one. With an array or a `Set`, each box contributes its `value` attribute: checking it adds that value, unchecking removes it, and a box renders checked when its value is in the collection. Vue does not mutate the array in place; it assigns a new array (or a new `Set`) through the `v-model` update. So a group model must start as `ref([])` or `ref(new Set())`, and each box needs a distinct `value` — here the value attribute is the payload, not something v-model ignores.
code
vue · 18 lines<script setup lang="ts">
import { ref } from 'vue'
const subscribed = ref(false)
const topics = ref<string[]>(['releases'])
</script>
<template>
<label>
<input type="checkbox" v-model="subscribed" /> Send me the newsletter
</label>
<fieldset :disabled="!subscribed">
<label><input type="checkbox" value="releases" v-model="topics" /> Releases</label>
<label><input type="checkbox" value="tutorials" v-model="topics" /> Tutorials</label>
<label><input type="checkbox" value="events" v-model="topics" /> Events</label>
</fieldset>
<p>Topics: {{ topics.join(', ') }}</p>
</template>go deeper
Recall the two shapes: a boolean for one box, an array for a group where each box has its own value. Be ready to write the group markup from memory.
Explain that the mode comes from the bound value's type at runtime, that Sets work too, and that Vue assigns a new array on each change rather than mutating.
Diagnose the classic bugs: a null-initialised group that checks every box, missing value attributes, and code holding a stale array reference after a click.
Weigh array versus Set models for large option lists, and when to derive an ordered selection with a computed instead of trusting click order.
## Two modes, chosen by the bound value In Vue 3, `v-model` on `<input type="checkbox">` compiles to a runtime directive (`vModelCheckbox` in `runtime-dom`) that decides what to do **every time the box changes**, by looking at the current type of the value it is bound to: - If the bound value is an **array**, the box works as one member of a group. - If it is a **`Set`**, the box works as a group member too, and Vue writes back a new `Set`. - Anything else — `true`, `false`, `null`, a string — puts the box in **single (boolean) mode**. That decision is made from the data, not from the template. Nothing in the markup says "this is a group"; three checkboxes bound to the same array are a group only because the array is an array. ## Boolean mode: one box, one flag In the newsletter preferences form, "Send me the newsletter" is a single box: - Checked writes `true`, unchecked writes `false`. - If the element has `true-value` / `false-value` (Vue-specific attributes that only work with `v-model`), those values are written instead. - The box renders checked when the model loosely equals the "checked" value. The browser's own form value for an unvalued checkbox is the string `"on"`, but that is what a native form **submission** sends; `v-model` stores `true`, not `"on"`. ## Array and Set mode: many boxes, one collection The topic picker is a group: three boxes, `value="releases"`, `value="tutorials"`, `value="events"`, all bound to `topics = ref<string[]>([])`. On each `change` event Vue runs roughly this: 1. Read the box's value — the raw value from a `:value` binding if there is one, otherwise the element's `value` string. 2. Look for it in the current array with loose equality. 3. If the box is now checked and the value is missing, assign `topics.concat(value)`. 4. If the box is now unchecked and the value is present, assign a copy with that entry spliced out. 5. On re-render, each box sets `checked` to whether its own value is in the array. Two consequences follow from steps 3 and 4: - **Order is click order.** Values are appended as they are checked, so the array does not follow the order of the boxes on the page. Sort it, or derive an ordered list with a `computed`, if order matters. - **The array is replaced, not mutated.** Because Vue assigns a new array through the `v-model` update, a plain `watch(topics, …)` on the ref fires without `deep`, and any code that kept a reference to the old array still holds the old contents. For a `Set` model the same logic applies with a cloned `Set`: `add` on check, `delete` on uncheck, then assign the clone. ## Where the value attribute matters — and where it does not On a text input, `v-model` ignores the `value` attribute: the bound state is the only source of truth. A checkbox group is the opposite case. There the `value` attribute is **the payload**: it is what goes into the array. So: | Situation | What lands in the model | |---|---| | Single box, no extra attributes | `true` / `false` | | Single box with `true-value="yes"` `false-value="no"` | `'yes'` / `'no'` | | Group box with `value="events"` | `'events'` added or removed | | Group box with `:value="topic.id"` | the raw number or object, added or removed | | Group box with no `value` at all | the browser default string `'on'` — the same for every box | What *is* ignored on a checkbox is a static `checked` attribute: the initial state always comes from the model. ## Mistakes interviewers listen for - **Starting the model as `null` or `''`.** That is boolean mode. The first click writes `true`, and because every box then compares `true` with its own "checked" value, every box in the group renders checked. - **Forgetting `value`.** Every box contributes the same default string, so the boxes cannot be told apart. - **Expecting in-place mutation.** Code holding the previous array, or a watcher on a copy of it, sees stale contents. - **Expecting markup order.** The array is in the order the user clicked. ```vue <script setup lang="ts"> import { ref, watch } from 'vue' const topics = ref<string[]>([]) watch(topics, (next) => console.log('topics now', next)) </script> <template> <label><input type="checkbox" value="releases" v-model="topics" /> Releases</label> <label><input type="checkbox" value="tutorials" v-model="topics" /> Tutorials</label> </template> ``` The watcher above fires on every click without `deep: true`, because each click replaces `topics.value` with a new array.
- In a Vue 3 checkbox group, in what order do the values appear in the bound array?In the order the user checked them. Vue appends a newly checked value with `concat` and removes an unchecked one from wherever it sits, so the array never re-sorts to match the boxes on the page. If the order matters, sort it or derive an ordered list with a `computed` over the known options.
- What happens in Vue 3 if a checkbox group's model starts as `ref(null)` instead of `ref([])`?The boxes run in single boolean mode, because `null` is neither an array nor a `Set`. The first click writes `true`, and each box then compares `true` with its own checked value, so the whole group renders checked. Initialize the model as `[]` or `new Set()`.
- Does Vue 3's checkbox v-model push into the bound array in place?No. It builds a new array — `concat` on check, a spliced copy on uncheck — and assigns it through the `v-model` update, so `topics.value` is replaced. A watcher on the ref fires without `deep`, and anything holding the old array keeps the old contents.
A checkbox group is a guest list on a clipboard: each box is a person who writes their own name on the list when they check in and crosses it off when they leave. A person with no name tag writes 'on', and so does everyone else without one.
saying these in an interview costs you the question
- v-model ignores the value attribute on checkboxes, so group boxes need no value.
- A checkbox group model can start as null; Vue turns it into an array.
- v-model pushes into the existing array, so the same array instance is kept.
- The bound array always follows the order the boxes appear on the page.
- A single checkbox with v-model stores the string 'on' when checked.