skip to content

In Vue 3, why does v-model ignore a value, checked or selected attribute written in the template, and where must the initial value come from?

level: middleimportance: should knowfreq 46%

answer

  1. one source of truth
  2. overwritten when the element mounts
  3. compiler flags :value beside v-model
  4. unmatched select renders unselected

basics

~20 s

v-model treats the bound JavaScript state as the only source of truth, so on mount it overwrites the element's value, checked or selected state from the model. Declare the initial value in the ref or reactive state, not in the markup.

solid answer

~40 s

`v-model` is a two-way binding whose authority is the component state. When the element mounts, Vue writes the model into it: a text input gets the model's value (or `''` for `null`/`undefined`), a checkbox or radio gets `checked` from the model, and a `<select>` selects the option that matches. Whatever the template said in `value`, `checked` or `selected` is overwritten, so `<input value="Guest" v-model="name">` with `name = ref('')` renders empty. The template compiler also flags a `:value` binding next to `v-model` on a text input or textarea. The fix is to seed the state: `ref('Guest')`. On radios and group checkboxes the `value` attribute is not ignored — it is the value the element contributes.

code

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

// seed the state, not the markup
const displayName = ref('Guest')
const frequency = ref('weekly')
const region = ref('')
</script>

<template>
  <input v-model="displayName" />
  <label><input type="radio" value="weekly" v-model="frequency" /> Weekly</label>
  <label><input type="radio" value="monthly" v-model="frequency" /> Monthly</label>
  <select v-model="region">
    <option disabled value="">Choose a region</option>
    <option value="eu">Europe</option>
    <option value="na">North America</option>
  </select>
</template>

go deeper

for a junior

Remember that the ref or reactive state supplies the first value; value, checked and selected in the markup are overwritten.

for a middle

Explain what each form directive writes on mount, and why value is ignored on text fields but used on radios, group checkboxes and options.

for a senior

Catch the related production bugs: a :value competing with v-model, an unmatched select rendering blank, and early typed text around hydration.

for a principal

Set the team convention that form state is seeded in one place — the component state or a form store — so markup never carries hidden defaults.

## One source of truth The Vue guide states the rule directly: `v-model` ignores the initial `value`, `checked` or `selected` attributes on form elements and always treats the bound JavaScript state as the source of truth. The initial value belongs in `ref()` / `reactive()` (or `data()` in the Options API). The reason is design, not accident. If both the markup and the state could supply the first value, the component would render one thing on the server, another on the client, and a third after the first update. Making the state the single authority means the element always shows what the model says, and the only way the model changes is through the element's events or your own code. ## What Vue does when the element is set up Each form element type gets its own runtime directive, and each writes the model into the element: 1. **Text inputs and textareas** (`vModelText`) set `el.value` to the model value when the element mounts, using `''` when the model is `null` or `undefined`. 2. **Checkboxes** (`vModelCheckbox`) set `checked` from the model when the element mounts, after `true-value`/`false-value` are available. 3. **Radios** (`vModelRadio`) set `checked` as soon as the element is created, by comparing the model with the radio's own value. 4. **Selects** (`vModelSelect`) select the option whose value matches the model once the options exist; with `multiple` they select every matching option. So in the newsletter form: ```vue <script setup lang="ts"> import { ref } from 'vue' const displayName = ref('') </script> <template> <!-- renders an empty field: the model wins --> <input value="Guest" v-model="displayName" /> </template> ``` The field is empty on mount, and the first keystroke writes whatever the user typed — `'Guest'` never reaches the model. ## Not every value attribute is ignored The word "value" means two different things in form markup, and interviewers probe the difference: | Element | Attribute | What v-model does with it | |---|---|---| | text input / textarea | `value`, `:value` | ignored as a starting value; the model is written over it | | checkbox (single) | `checked` | ignored; `checked` comes from the model | | checkbox (in an array group) | `value` / `:value` | **used**: it is the entry added to or removed from the array | | radio | `value` / `:value` | **used**: it is what the model becomes when this radio is picked | | radio / checkbox | `checked` | ignored | | `<option>` | `value` / `:value` | **used**: it is what the model becomes when selected | | `<option>` | `selected` | ignored | So "v-model ignores the value attribute" is true for text fields and false for choice controls. ## What the compiler and runtime tell you - On a text input or textarea, a `:value` binding next to `v-model` makes Vue's template compiler (in development builds) report: *"Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior."* - Interpolation inside a textarea — `<textarea>{{ bio }}</textarea>` — does not work; the guide says to use `v-model` instead. - `v-model` on `<input type="file">` is a compile error: file inputs are read-only, and the message tells you to use a `v-on:change` listener. ## The select trap If the model's initial value matches no option, Vue sets the select's `selectedIndex` to `-1`: the control renders **unselected**. The Vue guide warns that on iOS this can stop the user from choosing the first option, because no `change` event fires for it, and recommends a disabled option with an empty value as the first entry: ```vue <select v-model="region"> <option disabled value="">Choose a region</option> <option value="eu">Europe</option> <option value="na">North America</option> </select> ``` With `region = ref('')` the placeholder is selected, and every real option produces a `change`. ## A hydration edge case Server-side rendering is the one place the markup's text can win. Since the 3.5.41 patch release, when a plain `type="text"` input or a textarea is hydrated and its current text differs from the server-rendered default — the user typed before the JavaScript arrived — Vue keeps the typed text and writes it into the model instead of wiping it. This is a recovery path for early input, not a way to seed state; the rule for authoring stays the same.

  • In Vue 3, what does the template compiler report for <input :value="a" v-model="b">?
    In development builds it reports "Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior." `v-model` already binds the value, so a second binding competes with it. Remove the `:value` and seed `b` instead.
  • Why does the Vue guide recommend a disabled empty option as the first entry of a v-model select?
    If the model matches no option, Vue renders the select unselected (`selectedIndex` of -1). On iOS no `change` event fires when the user picks the first option from that state, so the model never updates. A disabled option with `value=""` matches an empty-string model and removes the gap.

saying these in an interview costs you the question

  • A value attribute on a text input sets v-model's starting value.
  • v-model ignores the value attribute on radios and checkbox groups too.
  • Adding :value next to v-model on a text input is a harmless way to seed it.
  • A select whose model matches no option selects the first option.
  • The selected attribute on an option decides a v-model select's first state.