Learn and Teach Coding
JS

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.

JavaScriptBeginnerformsvalidationvanilla-jshtml

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 2

Build a login screen with plain HTML and JavaScript, then add real input validation — same stack, end to end.

Related examples

JSBeginner

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 example
Beginner

Form 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