[JavaScript Series 8] Designing Form Validation and User Feedback

한국어 버전

Now that your basic form structure is solid, it is time to guide users by checking their input and showing helpful feedback. Even if the server validates again, clear client-side rules lower user fatigue.

Key terms

  1. Form validation: checking whether input values follow the rules before submission.
  2. Regular expression (regex): a pattern used to verify formats like an email address.
  3. blur: the event fired when an input loses focus, often used to validate after typing.
  4. aria-live: accessibility attribute that tells assistive tech to read live messages.
  5. aria-invalid: signals to assistive tech that the current field is invalid.

Core ideas

  • Validation rules: track requirements such as required fields, length, pattern, or matching values.
  • Validation timing: decide whether to show messages while typing, on blur, or at submit time.
  • Feedback elements: plan warning text, border color, and aria-live regions for visual and auditory cues.
  • Accessibility: attributes like aria-invalid and aria-live="polite" make sure messages are read aloud.

Code examples

const constraints = {
  name: { required: true, minLength: 2 },
  email: { required: true, pattern: /^[\w.-]+@([\w-]+\.)+[\w-]{2,}$/ },
  password: { required: true, minLength: 8 },
  confirmPassword: { matches: "password" },
};

function validateField(name, value, form) {
  const rules = constraints[name];
  if (!rules) return { valid: true };
  if (rules.required && !value.trim()) return { valid: false, message: "This field is required." };
  if (rules.minLength && value.length < rules.minLength)
    return { valid: false, message: `Please enter at least ${rules.minLength} characters.` };
  if (rules.pattern && !rules.pattern.test(value))
    return { valid: false, message: "Please follow the requested format." };
  if (rules.matches) {
    const target = form.elements.namedItem(rules.matches);
    if (target && value !== target.value) return { valid: false, message: "Values do not match." };
  }
  return { valid: true };
}
const form = document.querySelector(".signup-form");
const feedbackEl = document.querySelector(".form-feedback");

form?.addEventListener("input", (event) => {
  const target = event.target;
  if (!(target instanceof HTMLInputElement) || !form) return;
  const { valid, message } = validateField(target.name, target.value, form);
  target.classList.toggle("is-invalid", !valid);
  target.setAttribute("aria-invalid", String(!valid));
  const hint = target.nextElementSibling;
  if (hint) hint.textContent = message ?? "";
});

form?.addEventListener("submit", (event) => {
  event.preventDefault();
  if (!form) return;
  const fields = Array.from(form.elements).filter((el) => el instanceof HTMLInputElement);
  const invalid = fields
    .map((field) => ({ field, result: validateField(field.name, field.value, form) }))
    .find(({ result }) => !result.valid);

  if (invalid) {
    if (feedbackEl) feedbackEl.textContent = invalid.result.message;
    invalid.field.focus();
    return;
  }

  if (feedbackEl) {
    feedbackEl.textContent = "Sign-up request submitted.";
    feedbackEl.classList.add("is-success");
  }
});

Splitting real-time and submit-time validation makes it clear what to fix and when.

.signup-form input {
  border: 1px solid #c8d0da;
  transition: border-color 0.2s ease;
}

.signup-form input.is-invalid {
  border-color: #ff5757;
  background: rgba(255, 87, 87, 0.1);
}

.signup-form .hint {
  font-size: 0.85rem;
  color: #ff5757;
  min-height: 1.2rem;
}

.form-feedback {
  margin-top: 1rem;
  font-weight: 600;
}

.form-feedback.is-success {
  color: #1ba784;
}

Just a little CSS makes invalid fields obvious.

Replay the flow with BrowserMock

Form validation follows the sequence of input → message → submit, so replaying UI state with BrowserMock, a lightweight mock browser tool, is handy.


const { document, window } = mock(`
  <form class="signup-form">
    <input name="email" />
    <span class="hint"></span>
    <div class="form-feedback"></div>
  </form>
`);

const form = document.querySelector(".signup-form");
const feedbackEl = document.querySelector(".form-feedback");

form.addEventListener("submit", (event) => {
  event.preventDefault();
  const { valid, message } = validateField("email", form.email.value, form);
  if (!valid) feedbackEl.textContent = message;
});

form.email.value = "broken";
form.dispatchEvent(new window.Event("submit"));
console.log(feedbackEl.textContent); // "Please follow the requested format."

You can confirm that error messages show up right on time without launching a real browser. During reviews, capture BrowserMock console logs to explain state transitions and reach agreement quickly.

Why it matters

  • Client-side validation catches mistakes before a request hits the server, saving bandwidth and frustration.
  • Clear feedback builds trust for critical flows like admissions or club sign-up forms.
  • Accessible messages give the same information to people who rely on assistive tech.

Practice

  • Follow along: wire up validateField along with the input/submit handlers in a single sign-up form.
  • Extend: add a password-strength meter, phone-number masking, or merge server errors, choosing any two.
  • Debug: break the regex or matches rule on purpose, watch which message appears, and confirm events do not fire twice in DevTools.
  • Done when: invalid fields block submission, success text turns green, and there are no console warnings.

Wrap-up

Solid validation makes users trust the experience. Next we will connect web storage and persistence so those inputs stay safe.

💬 댓글

이 글에 대한 의견을 남겨주세요