Forms and v-model

Introduction

Forms collect user input—text, choices, and files. v-model creates two-way binding between form controls and reactive state. This chapter covers native inputs, modifiers, v-model on custom components, and simple validation patterns without a full UI library.

Prerequisites

Text Inputs

Code explanation:

  • v-model on input / textarea binds value
  • Always associate <label> for accessibility

Modifiers

vue
<input v-model.lazy="title" placeholder="Updates on blur" />
<input v-model.number="age" type="number" />
<input v-model.trim="username" />
ModifierBehavior
.lazySync on change instead of input
.numberCast to number
.trimTrim whitespace

Combine: v-model.trim.lazy.

Checkbox and Radio

Single checkbox (boolean):

vue
<script setup lang="ts">
const agreed = ref(false);
</script>
 
<template>
  <label>
    <input v-model="agreed" type="checkbox" />
    I agree to terms
  </label>
</template>

Multiple checkboxes (array):

vue
<script setup lang="ts">
const selected = ref<string[]>([]);
const options = ["Vue", "React", "Svelte"];
</script>
 
<template>
  <label v-for="opt in options" :key="opt">
    <input v-model="selected" type="checkbox" :value="opt" />
    {{ opt }}
  </label>
  <p>Picked: {{ selected.join(", ") }}</p>
</template>

Radio group:

vue
<script setup lang="ts">
const plan = ref("free");
</script>
 
<template>
  <label>
    <input v-model="plan" type="radio" value="free" /> Free
  </label>
  <label>
    <input v-model="plan" type="radio" value="pro" /> Pro
  </label>
</template>

Select

Single select:

vue
<script setup lang="ts">
const country = ref("");
</script>
 
<template>
  <select v-model="country">
    <option disabled value="">Choose…</option>
    <option value="us">United States</option>
    <option value="jp">Japan</option>
  </select>
</template>

Multiple select:

vue
<select v-model="tags" multiple>
  <option value="vue">Vue</option>
  <option value="ts">TypeScript</option>
</select>

Form Submit

.prevent on submit avoids full page reload.

v-model on Components

Default custom v-model uses modelValue prop and update:modelValue event:

vue
<!-- TextInput.vue -->
<script setup lang="ts">
const model = defineModel<string>({ default: "" });
</script>
 
<template>
  <input v-model="model" class="text-input" />
</template>

Parent:

vue
<TextInput v-model="username" />

Vue 3.4+ defineModel simplifies the old pattern:

Multiple v-model Names

vue
<!-- UserForm.vue -->
<script setup lang="ts">
const firstName = defineModel<string>("firstName", { default: "" });
const lastName = defineModel<string>("lastName", { default: "" });
</script>
 
<template>
  <input v-model="firstName" placeholder="First" />
  <input v-model="lastName" placeholder="Last" />
</template>

Parent:

vue
<UserForm v-model:first-name="first" v-model:last-name="last" />

Use kebab-case in templates for multi-word props.

Simple Validation (No Library)

Track touched state and errors:

For large forms, libraries like VeeValidate or UI kits (Element Plus) help—link their official docs when needed.

Warning

Client-side validation improves UX—it does not replace server validation on FastAPI or Django.

Native HTML5 Validation

vue
<form @submit.prevent="onSubmit">
  <input v-model="title" required minlength="3" maxlength="100" />
  <input v-model="email" type="email" required />
  <button type="submit">Save</button>
</form>

Browser shows built-in messages; style with ::backdrop / pseudo selectors or call reportValidity() in script.

FAQ

v-model vs
+ @input?

v-model is sugar for the pair—use v-model unless you need custom parsing.

Why checkbox array empty?

Ensure each checkbox value attribute is set.

defineModel requirements?

Vue 3.4+; enable in vue version from create-vue.

File inputs?

v-model on <input type="file"> binds File list—handle upload separately.

Controlled vs uncontrolled?

Vue components are controlled—state lives in script, not only in DOM.

Form libraries?

Optional for this track—native + computed errors teach the pattern first.