Build a Form Validator with JavaScript
Build a form validator with plain JavaScript that checks required fields, validates an email format, and shows clear error messages before submission.
A JavaScript form validator checks that every field in a form is filled in correctly before the form is allowed to submit. It builds on the form submission pattern from other mini projects, but adds a validation step in between: the click is caught, checked against a set of rules, and only allowed through if every rule passes.
You will build a form with a name, email, and password field, write a small check for each one, and show clear error messages when a field fails.
What You Will Build
The validator needs to support:
- Blocking submission until every required field has a value.
- Checking that the email field looks like a real email address.
- Showing an error message next to each invalid field.
- Allowing the form to submit once every check passes.
Every submission runs through the same two-branch decision: if any check fails, the user sees exactly what to fix, and if every check passes, the form is allowed to continue. The steps below build each piece of that decision.
Step 1: Build the HTML
Start with a form that has a spot for an error message under each field.
<form id="signupForm">
<input type="text" id="name" placeholder="Name" />
<span class="error" id="nameError"></span>
<input type="text" id="email" placeholder="Email" />
<span class="error" id="emailError"></span>
<input type="password" id="password" placeholder="Password" />
<span class="error" id="passwordError"></span>
<button type="submit">Sign Up</button>
</form>Each field has its own error element right below it. Placing errors near the field they belong to is clearer for the user than one general error message at the top of the form.
Step 2: Catch the Submission Before It Reloads the Page
Listen for the submit event, and stop the browser's default reload before running any checks.
const form = document.getElementById("signupForm");
form.addEventListener("submit", (event) => {
event.preventDefault();
const errors = validateForm();
if (errors.length === 0) {
form.submit();
}
});event.preventDefault() stops the browser from reloading the page immediately, which gives the validation code a chance to run first. The handler collects every error into an array, and only calls the real submit when that array comes back empty. For more on this event, see the using preventDefault in JavaScript guide.
Step 3: Check Required Fields
Write a small function that checks a field is not left empty, and shows an error if it is.
function validateRequired(inputId, errorId, message) {
const input = document.getElementById(inputId);
const errorEl = document.getElementById(errorId);
if (input.value.trim() === "") {
errorEl.textContent = message;
return message;
}
errorEl.textContent = "";
return null;
}This function reads one field's value, and either sets an error message next to it or clears any error from a previous attempt. It returns the message string when the field fails, or null when it passes, so the calling code can tell the two cases apart.
Step 4: Check the Email Format
Email needs an extra check beyond just being non-empty, using a regular expression to catch an obviously invalid format.
function validateEmail(inputId, errorId) {
const input = document.getElementById(inputId);
const errorEl = document.getElementById(errorId);
const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!pattern.test(input.value.trim())) {
errorEl.textContent = "Enter a valid email address";
return "Enter a valid email address";
}
errorEl.textContent = "";
return null;
}The pattern checks for text, an @ symbol, more text, a dot, then more text, which catches obvious mistakes like a missing @ or a missing domain. It cannot confirm the address is real, only that it is shaped like one.
Step 5: Check the Password Length
The password field needs its own rule too, since a required check alone would accept a single character.
function validatePassword(inputId, errorId) {
const input = document.getElementById(inputId);
const errorEl = document.getElementById(errorId);
if (input.value.length < 8) {
errorEl.textContent = "Password must be at least 8 characters";
return "Password must be at least 8 characters";
}
errorEl.textContent = "";
return null;
}This follows the same shape as validateEmail, but checks length instead of matching a pattern. Any field-specific rule can be added this way: read the value, check it, set or clear the error message, and return the message or null.
Step 6: Combine Every Check
The main validation function calls each individual check and collects the results.
function validateForm() {
const results = [
validateRequired("name", "nameError", "Name is required"),
validateEmail("email", "emailError"),
validatePassword("password", "passwordError"),
];
return results.filter((message) => message !== null);
}Each check runs regardless of whether an earlier one failed, so the user sees every problem at once instead of one at a time. Filtering out the null values leaves only the real error messages, and an empty array means the form is ready to submit. For a deeper look at rules like this, see the form validation with JavaScript guide.
Common Mistakes
| Mistake | Why it breaks | Fix |
|---|---|---|
Forgetting event.preventDefault() | The page reloads before any validation code can run | Call it as the first line inside the submit handler |
| Stopping at the first failed field | The user has to resubmit repeatedly to find every mistake | Run every check and collect all the error messages together |
| Treating a passed email pattern as a verified address | A regular expression only checks the shape of the text | Use client-side validation for early feedback, and verify the address for real on the server |
Next Step
You have now built the full set of DOM, state, and array patterns used across this mini project series. If you have not built the JavaScript shopping cart yet, it is a good next step for practicing the same event delegation and array-driven rendering used here.
Rune AI
Key Insights
- Call event.preventDefault() first so the page does not reload before validation can run.
- Write one small function per rule, such as checking a field is not empty or matches an email pattern.
- Collect every error into an array instead of stopping at the first one, so the user sees all the problems at once.
- Only allow the form to submit for real once the error array comes back empty.
- Show error messages near the field they belong to, not only in one general message at the top.
Frequently Asked Questions
Why block form submission with event.preventDefault() before validating?
Is a regular expression enough to fully validate an email address?
Why collect all the error messages before showing any of them?
Conclusion
A form validator combines an event you already know from other projects, form submission, with checks that run before anything is sent. Blocking submission until every field passes, then showing every problem at once, is the same approach real signup and checkout forms use to catch mistakes before they reach a server.
More in this topic
Using Reflect and Proxy Together in JavaScript
Proxy traps intercept operations, but Reflect is what makes them behave correctly. Learn why Reflect belongs inside every trap and what breaks when you skip it.
Top JS Array Methods Interview Questions to Know
The array method questions that come up most often in JavaScript interviews, answered directly with short examples: map vs forEach, mutating vs non-mutating methods, reduce, and more.
JavaScript Reflect API: Advanced Architecture
The Reflect object exposes JavaScript's own internal operations as plain functions. Learn every Reflect method, what it returns, and why it exists as its own API.