Vue Forms
Collect, validate, and submit user input with Vue form bindings.
Overview
Forms collect information for login, registration, search, feedback, checkout, and many other workflows. Vue's v-model directive synchronizes compatible form controls with reactive state, while event handling and validation keep submission logic clear and responsive.
Key concepts
- v-model synchronizes controls and state
- Different controls produce different value shapes
- @submit.prevent handles client-side submission
- Accessible labels and validation improve usability
What is a Form?
A form is a section of an interface that collects user information. It can contain text inputs, textareas, checkboxes, radio groups, select menus, file controls, and action buttons.
- Login and registration
- Contact and feedback
- Search
- Checkout and payment
- Profile editing
- Booking
Why do we need Forms?
An online order requires a name, email, shipping address, phone number, and payment details. Vue connects those controls to component state so the application can validate and process one structured data model.
Using v-model
v-model creates a two-way binding: user input updates state, and programmatic state changes update the control.
<script setup>
import { ref } from "vue";
const name = ref("");
</script>
<template>
<label for="name">Name</label>
<input id="name" v-model="name">
<p>Hello, {{ name }}</p>
</template>Text Input
<label for="username">Username</label>
<input id="username" v-model="username" autocomplete="username">Textarea
Bind textarea content with v-model rather than placing interpolation between its tags.
<label for="message">Message</label>
<textarea id="message" v-model="message"></textarea>Boolean Checkbox
A single checkbox normally binds to a Boolean.
<label>
<input v-model="accepted" type="checkbox">
I accept the terms
</label>Multiple Checkboxes
Checkboxes sharing an array model add and remove their value attributes.
<label><input v-model="interests" type="checkbox" value="Vue"> Vue</label>
<label><input v-model="interests" type="checkbox" value="DSA"> DSA</label>Radio Buttons
Radio controls sharing one model allow one selected value. Give the group a meaningful fieldset and legend when possible.
<fieldset>
<legend>Plan</legend>
<label><input v-model="plan" type="radio" value="basic"> Basic</label>
<label><input v-model="plan" type="radio" value="pro"> Pro</label>
</fieldset>Select Dropdown
<label for="country">Country</label>
<select id="country" v-model="country">
<option disabled value="">Choose a country</option>
<option value="IN">India</option>
<option value="US">USA</option>
<option value="CA">Canada</option>
</select>v-model Modifiers
| Modifier | Behavior |
|---|---|
| .trim | Removes surrounding whitespace |
| .number | Converts input to a number when possible |
| .lazy | Updates on change instead of each input event |
<input v-model.trim="name">
<input v-model.number="age" type="number">
<input v-model.lazy="notes">Handling Form Submission
Listen on the form's submit event so keyboard submission works as well as clicking the button. The .prevent modifier stops the browser's default navigation.
<script setup>
import { reactive } from "vue";
const form = reactive({
name: "",
email: ""
});
async function submitForm() {
console.log({ ...form });
}
</script>
<template>
<form @submit.prevent="submitForm">
<label for="full-name">Name</label>
<input id="full-name" v-model.trim="form.name" required>
<label for="email">Email</label>
<input id="email" v-model.trim="form.email" type="email" required>
<button type="submit">Submit</button>
</form>
</template>Validation
Use native HTML constraints for basic feedback and application validation for business rules. Never rely only on client-side validation; the server must validate submitted data again.
<p v-if="emailError" id="email-error" role="alert">
{{ emailError }}
</p>
<input
v-model.trim="email"
type="email"
aria-describedby="email-error"
:aria-invalid="Boolean(emailError)"
>Submission State
Track pending, success, and failure states. Disable duplicate submission while a request is running and provide visible feedback.
<button type="submit" :disabled="isSubmitting">
{{ isSubmitting ? "Saving..." : "Save" }}
</button>
<p v-if="submitError" role="alert">{{ submitError }}</p>File Inputs
v-model does not bind file inputs. Read selected File objects from the change event.
<input type="file" @change="file = $event.target.files[0]">Real-life example
A registration form binds name, email, password, and country to one reactive model. On submit, it validates fields, sends an API request, disables repeat submission, reports errors, and redirects or confirms success.
Common beginner mistakes
- Forgetting to initialize model values
- Omitting @submit.prevent
- Using strings when numbers are required
- Binding multiple checkboxes to a Boolean instead of an array
- Using v-model on a file input
- Putting extensive validation logic in the template
- Skipping labels
- Trusting client-side validation as security
Best practices
- Associate every control with a label
- Use the correct input type and autocomplete value
- Group related form state
- Validate on the client and server
- Show errors close to their fields
- Preserve entered values after validation errors
- Prevent duplicate submissions
- Break very large forms into focused components
- Keep sensitive values out of logs
Key takeaways
- Forms collect user information
- v-model synchronizes controls and reactive state
- Controls produce Boolean, scalar, array, or file values depending on type
- Modifiers normalize common input behavior
- @submit.prevent handles client submission
- Accessible labels and errors are essential
- Submission needs loading and failure states
- Server-side validation remains mandatory