Form Validation in HTML & JavaScript
Add real validation to the plain HTML login form — custom rules, inline error messages, validate-on-blur, and a submit guard. No libraries.
Source code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sign in</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main class="card">
<header class="card__header">
<h1>Welcome back</h1>
<p>Sign in to your account to continue</p>
</header>
<!-- novalidate: turn OFF the browser's built-in bubbles so we can render
our own inline messages. We still keep the constraints as hints. -->
<form id="login-form" novalidate>
<div class="field">
<label for="email">Email</label>
<input
id="email"
name="email"
type="email"
autocomplete="email"
aria-describedby="email-error"
/>
<!-- aria-live so screen readers announce the error when it appears. -->
<p class="error" id="email-error" aria-live="polite"></p>
</div>
<div class="field">
<label for="password">Password</label>
<input
id="password"
name="password"
type="password"
autocomplete="current-password"
aria-describedby="password-error"
/>
<p class="error" id="password-error" aria-live="polite"></p>
</div>
<button type="submit">Sign in</button>
</form>
<p id="success" class="success" role="status" hidden></p>
</main>
<script src="validation.js"></script>
</body>
</html>Walkthrough
This picks up where the plain HTML & JavaScript login form left off. The form already submits — now we add real validation: custom rules, inline error messages that appear as the user leaves each field, and a submit guard that blocks bad input. Still no libraries and no framework.
This is the vanilla-JS counterpart of the React Simple Input Validation example — same behavior, two stacks. The cross-links below jump between them.
1. A validator per field
Instead of relying on the browser's built-in bubbles (we turned those off with
novalidate), we write our own rules. Each returns an error string, or "" when valid:
const validators = {
email(value) {
if (!value.trim()) return "Email is required.";
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return "Enter a valid email address.";
return "";
},
password(value) {
if (!value) return "Password is required.";
if (value.length < 6) return "Password must be at least 6 characters.";
return "";
},
};2. Showing the message
validateField runs one field's rule, writes the message into its <p class="error">,
toggles a red-border class, and sets aria-invalid for assistive tech:
function validateField(input) {
const message = validators[input.name](input.value);
const errorEl = document.getElementById(`${input.name}-error`);
errorEl.textContent = message;
input.classList.toggle("is-invalid", Boolean(message));
input.setAttribute("aria-invalid", message ? "true" : "false");
return !message;
}3. Validate on blur, clear on type
Checking on blur gives feedback the moment the user leaves a field — not on every
keystroke, which feels noisy. Once an error is showing, we re-check on input so it
disappears as soon as they fix it:
input.addEventListener("blur", () => validateField(input));
input.addEventListener("input", () => {
if (input.classList.contains("is-invalid")) validateField(input);
});4. The submit guard
On submit we validate every field (using .map, not .some, so all errors surface
at once) and bail if any failed, moving focus to the first invalid field:
const results = Object.keys(validators).map((name) =>
validateField(form.elements[name]),
);
if (results.includes(false)) {
form.querySelector(".is-invalid")?.focus();
return;
}Client-side validation is for UX, not security — always re-validate on the server. A user can bypass any of this from the dev tools.
That's hand-rolled validation in plain JavaScript. Compare it with the React version to
see how useState and re-renders change the shape of the same logic.
Compare: Form Validation in another stack
Hand-rolled input validation, the same rules in two stacks. Compare the React (useState) approach with plain JavaScript.
Login Form with HTML & JavaScript
Step 2 of 2Build a login screen with plain HTML and JavaScript, then add real input validation — same stack, end to end.
Related examples
Login Form in HTML & JavaScript
The same accessible login screen built with plain HTML, CSS, and vanilla JavaScript — no framework. A side-by-side reference for the React version.
View exampleForm Validation — Simple Input Validation
Validate a sign-in form by hand with useState — inline errors, validate-on-blur, and a submit guard. No libraries.
View example