DocsHub
JavascriptAdvanced

Form Validation

Build a multi-field form with real-time validation that shows errors inline and only submits when all fields are valid.

Form Validation

Problem

Build a registration form with real-time validation. Each field is validated when the user leaves it (blur) and errors are shown inline next to the field. The form only submits when every field passes validation.

User clicks submit without filling anything
→ All fields show their error messages

User types "a" in username and moves to next field
→ "Username must be at least 3 characters"

User types "notanemail" in email and moves on
→ "Please enter a valid email address"

User types "pass" in password
→ "Password must be at least 8 characters"

User types "differentpass" in confirm password
→ "Passwords do not match"

All fields valid → form submits → success message shown

Logic

  1. Define validation rules for each field
  2. Validate each field on blur — when the user leaves it
  3. Also validate on input — only if the field already has an error shown
  4. Show error message below the field and add an error style
  5. On form submit — validate all fields at once
  6. If any field fails — stop submission and show all errors
  7. If all pass — show a success message

Flow

no yes no yes User leaves a field Run validation for that field Valid? Show error messageAdd error style to input Clear error messageAdd success style User clicks submit Validate all fields All valid? Show all errorsStop submission Show success message

HTML Structure

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Form Validation</title>
    <style>
      body {
        font-family: sans-serif;
        max-width: 460px;
        margin: 60px auto;
        padding: 0 20px;
      }

      /* each field group — label, input, error */
      .field-group {
        margin-bottom: 20px;
      }

      .field-group label {
        display: block;
        font-weight: 500;
        margin-bottom: 6px;
        color: #333;
        font-size: 0.9rem;
      }

      .field-group input {
        width: 100%;
        padding: 12px;
        font-size: 1rem;
        border: 2px solid #ccc;
        border-radius: 8px;
        outline: none;
        box-sizing: border-box;
        transition: border-color 0.2s;
      }

      .field-group input:focus {
        border-color: #555;
      }

      /* valid field — green border */
      .field-group input.valid {
        border-color: #1a7a3f;
      }

      /* invalid field — red border */
      .field-group input.invalid {
        border-color: #c0392b;
      }

      /* error message below the input */
      .error-message {
        font-size: 0.82rem;
        color: #c0392b;
        margin-top: 5px;
        min-height: 18px;
        display: block;
      }

      button[type="submit"] {
        width: 100%;
        padding: 14px;
        font-size: 1rem;
        font-weight: 600;
        background: #111;
        color: #fff;
        border: none;
        border-radius: 8px;
        cursor: pointer;
        margin-top: 6px;
      }

      button[type="submit"]:hover {
        opacity: 0.85;
      }

      /* success message shown after valid submission */
      #success {
        display: none;
        background: #e6f9ee;
        color: #1a7a3f;
        border-radius: 8px;
        padding: 20px;
        text-align: center;
        font-weight: 500;
      }
    </style>
  </head>
  <body>
    <h1>Create Account</h1>

    <!-- success message — shown on valid submission -->
    <div id="success">
      ✅ Account created successfully! Welcome aboard.
    </div>

    <form id="register-form" novalidate>

      <!-- username -->
      <div class="field-group">
        <label for="username">Username</label>
        <input
          type="text"
          id="username"
          placeholder="At least 3 characters, no spaces"
        />
        <span class="error-message" id="username-error"></span>
      </div>

      <!-- email -->
      <div class="field-group">
        <label for="email">Email</label>
        <input
          type="email"
          id="email"
          placeholder="your@email.com"
        />
        <span class="error-message" id="email-error"></span>
      </div>

      <!-- password -->
      <div class="field-group">
        <label for="password">Password</label>
        <input
          type="password"
          id="password"
          placeholder="At least 8 characters"
        />
        <span class="error-message" id="password-error"></span>
      </div>

      <!-- confirm password -->
      <div class="field-group">
        <label for="confirm-password">Confirm Password</label>
        <input
          type="password"
          id="confirm-password"
          placeholder="Repeat your password"
        />
        <span class="error-message" id="confirm-password-error"></span>
      </div>

      <button type="submit">Create Account</button>

    </form>

    <script src="script.js"></script>
  </body>
</html>

Solution

// Step 1 — select form and inputs
const form = document.querySelector("#register-form");
const successMessage = document.querySelector("#success");

const fields = {
  username: document.querySelector("#username"),
  email: document.querySelector("#email"),
  password: document.querySelector("#password"),
  confirmPassword: document.querySelector("#confirm-password"),
};

// Step 2 — define validation rules for each field
// each rule returns null if valid or an error message string if invalid
const validators = {
  username(value) {
    if (!value) return "Username is required.";
    if (value.length < 3) return "Username must be at least 3 characters.";
    if (value.length > 20) return "Username must be 20 characters or less.";
    if (/\s/.test(value)) return "Username cannot contain spaces.";
    if (!/^[a-zA-Z0-9_]+$/.test(value))
      return "Username can only contain letters, numbers, and underscores.";
    return null; // null means valid
  },

  email(value) {
    if (!value) return "Email is required.";
    // basic email regex — checks for something@something.something
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
      return "Please enter a valid email address.";
    return null;
  },

  password(value) {
    if (!value) return "Password is required.";
    if (value.length < 8) return "Password must be at least 8 characters.";
    if (!/[A-Z]/.test(value))
      return "Password must contain at least one uppercase letter.";
    if (!/[0-9]/.test(value))
      return "Password must contain at least one number.";
    return null;
  },

  confirmPassword(value) {
    if (!value) return "Please confirm your password.";
    // compare against the password field's current value
    if (value !== fields.password.value) return "Passwords do not match.";
    return null;
  },
};

// Step 3 — validate a single field by name
// returns true if valid, false if not
function validateField(name) {
  const input = fields[name];
  const errorEl = document.querySelector(`#${toKebab(name)}-error`);
  const value = input.value.trim();

  // run the validator for this field
  const error = validators[name](value);

  if (error) {
    // Step 4 — show error state
    input.classList.remove("valid");
    input.classList.add("invalid");
    errorEl.textContent = error;
    return false;
  } else {
    // Step 5 — show valid state
    input.classList.remove("invalid");
    input.classList.add("valid");
    errorEl.textContent = "";
    return true;
  }
}

// helper — converts camelCase to kebab-case
// "confirmPassword" → "confirm-password"
// needed to match the HTML id attributes
function toKebab(str) {
  return str.replace(/([A-Z])/g, "-$1").toLowerCase();
}

// Step 6 — attach blur listeners to all fields
// validate when user leaves a field
Object.keys(fields).forEach((name) => {
  fields[name].addEventListener("blur", () => {
    validateField(name);
  });

  // Step 7 — also validate on input — but only if the field already has an error
  // this gives live feedback once the user has already triggered validation
  fields[name].addEventListener("input", () => {
    if (fields[name].classList.contains("invalid")) {
      validateField(name);
    }
  });
});

// Step 8 — handle form submission
form.addEventListener("submit", (e) => {
  // always prevent default — we control submission
  e.preventDefault();

  // Step 9 — validate all fields at once
  const results = Object.keys(fields).map((name) => validateField(name));

  // Step 10 — check if all passed
  const allValid = results.every(Boolean);

  if (!allValid) {
    // find the first invalid field and focus it
    const firstInvalid = Object.keys(fields).find(
      (name) => fields[name].classList.contains("invalid")
    );
    if (firstInvalid) fields[firstInvalid].focus();
    return;
  }

  // Step 11 — all valid — show success
  form.style.display = "none";
  successMessage.style.display = "block";
});

Code Execution

Trace through submitting with empty fields:

FieldValueValidator resultOutcome
username"""Username is required."❌ invalid
email"""Email is required."❌ invalid
password"""Password is required."❌ invalid
confirmPassword"""Please confirm your password."❌ invalid
allValid[false, false, false, false].every(Boolean)falsesubmission blocked

Trace through username "a" on blur:

StepCodeResult
Value"a".trim()"a"
Run validatorvalidators.username("a")"Username must be at least 3 characters."
Apply errorinput.classList.add("invalid")red border
Show messageerrorEl.textContent = "..."error shown below

Trace through all valid fields:

FieldValueResult
username"ali_dev"✅ valid
email"ali@example.com"✅ valid
password"Password1"✅ valid
confirmPassword"Password1"✅ valid
allValid[true,true,true,true].every(Boolean)true
Actionform hidden, success shown

Output

Submit empty form          → All 4 fields show error messages
Type "a" in username       → "Username must be at least 3 characters"
Type valid username         → Green border, error cleared
Type "notanemail"           → "Please enter a valid email address"
Passwords do not match      → "Passwords do not match"
All fields valid + submit   → Form hidden, success message shown

On this page