In Vue 3, how do you make v-model store numbers, objects or custom on/off values from radios, checkboxes and select options?
answer
- static attributes are strings
- bind the value with a colon
- Vue-only true-value and false-value
- loose, structural comparison
basics
~20 sBind the choice with v-bind: a radio or option with :value stores the raw JavaScript value, and a checkbox's true-value and false-value set what checked and unchecked store. A static value attribute always yields a string.
solid answer
~40 sA static `value="7"` on a radio or `<option>` makes `v-model` store the string `'7'`. Writing `:value="7"` or `:value="{ code: 'eu' }"` stores the raw number or object: Vue keeps the bound value on the element and reads it back instead of the DOM string. For a single checkbox, `true-value` and `false-value` — Vue-specific attributes that only work with `v-model` — replace `true`/`false`, and can be bound with `:`. To decide which option or radio is checked, Vue compares loosely: objects and arrays structurally, primitives by their string form. `<select multiple>` needs an array or `Set` model. And `true-value` does not change what a native form submits, since unchecked boxes are not submitted at all.
go deeper
Remember that static attributes give strings, :value gives the real value, and a checkbox can store custom values with true-value and false-value.
Explain the stored raw value, loose structural comparison for objects and primitives compared as strings, and the array-or-Set rule for select multiple.
Spot type drift when one side is static and the other bound, and choose radios over true-value when a form posts natively.
Standardise how shared form controls map domain values to options, so API objects, ids and enums round-trip without ad hoc parsing.
## Why static attributes give you strings HTML attributes are strings. `<input type="radio" value="7" v-model="every">` puts the string `'7'` in `every` when picked, because the element's `value` property is a string. For a newsletter "send every N days" choice, that is usually not what the code wants: comparisons with numbers, `switch` statements and API payloads all expect `7`. ## Binding the raw value Vue 3 fixes this with ordinary `v-bind`: - **Radios:** `<input type="radio" :value="7" v-model="every">` stores the number `7`. - **Options:** `<option :value="{ code: 'eu', label: 'Europe' }">` stores that object when chosen. - **Group checkboxes:** `:value="topic.id"` adds the id (a number) to the array. Under the hood, when `value` is set on an input or option Vue also stores the raw bound value on the element (as a private `_value` property), and the `v-model` directives read that raw value back rather than the stringified DOM `value`. ## true-value and false-value for a single checkbox A single checkbox normally stores `true` or `false`. The Vue guide calls `true-value` and `false-value` **Vue-specific attributes that only work with `v-model`**: ```vue <input type="checkbox" v-model="consent" true-value="yes" false-value="no" /> <input type="checkbox" v-model="tier" :true-value="plans.pro" :false-value="plans.free" /> ``` Checked stores `'yes'`, unchecked stores `'no'`; the bound versions store whatever the expressions evaluate to. Two limits matter: - They do **not** change the input's `value` attribute, so a native form submission is unaffected. Browsers do not submit unchecked boxes at all, so if the server must receive one of two values, the guide recommends a pair of radios instead. - They apply to a single, non-group checkbox. In a group bound to an array, each box contributes its `value`. ## How Vue decides what is checked or selected When the model changes, Vue has to decide which radio is checked and which option is selected. It uses a **loose equality** helper rather than `===`: | Compared values | Rule | Example that matches | |---|---|---| | two objects | same keys, loosely equal values | model `{ code: 'eu' }` vs option `:value="{ code: 'eu' }"` (different objects) | | two arrays | same length, loosely equal items | `[1, 2]` vs `[1, 2]` | | two Dates | same timestamp | two `Date` objects for the same instant | | two primitives | equal after `String()` | `7` vs `'7'` | Consequences worth naming: 1. An option bound to an inline object literal still shows selected after the model is replaced by an equal object from an API response — identity is not required. 2. A model of `7` selects a radio with a static `value="7"`, but clicking that radio then stores `'7'`, changing the model's type. Bind both sides consistently. 3. Two options with structurally equal values are indistinguishable; the first match wins for a single select. ## select-specific rules - `<select v-model>` also accepts `.number`, which casts each selected option's value with `parseFloat`. - `<select multiple v-model>` expects an **array or `Set`**. With anything else, development builds warn "`<select multiple v-model> expects an Array or Set value for its binding, but got …`" and Vue leaves the options' selection untouched. - A `Set` model receives a new `Set`; an array model receives a new array. ## The newsletter form, typed end to end ```vue <script setup lang="ts"> import { ref } from 'vue' type Region = { code: string; label: string } const regions: Region[] = [ { code: 'eu', label: 'Europe' }, { code: 'na', label: 'North America' }, ] const every = ref(7) const consent = ref<'yes' | 'no'>('no') const region = ref<Region | null>(null) </script> <template> <label><input type="radio" :value="1" v-model="every" /> Daily</label> <label><input type="radio" :value="7" v-model="every" /> Weekly</label> <input type="checkbox" v-model="consent" true-value="yes" false-value="no" /> <select v-model="region"> <option :value="null" disabled>Choose a region</option> <option v-for="r in regions" :key="r.code" :value="r">{{ r.label }}</option> </select> </template> ``` Every model now holds the type the rest of the code expects, with no parsing step.
- In Vue 3, does a checkbox's true-value change what a native form submission sends?No. `true-value` and `false-value` only change what `v-model` stores; the input's `value` attribute is untouched. Browsers do not submit unchecked boxes at all, so the guide recommends two radios when the server must receive one of two values.
- In Vue 3, why does a <select v-model> option bound to an object literal stay selected when the model is replaced by an equal object?Vue compares the model with each option's raw value using loose equality, which compares objects structurally — same keys, loosely equal values — rather than by identity. An equal object from an API response therefore still matches the option.
- What does Vue 3 do if a <select multiple v-model> is bound to a string?In development it warns that `<select multiple v-model>` expects an Array or Set value for its binding, and it leaves the options' selection untouched. Initialise the model as `[]` or `new Set()`.
saying these in an interview costs you the question
- A static value="7" on a radio makes v-model store the number 7.
- true-value and false-value also change what a native form submits.
- Object option values match only if the model is the same object instance.
- select multiple converts a string model into a one-item array.
- true-value is a standard HTML attribute that works without v-model.