Vanilla JavaScript Form Validation: 7 Steps to Build It Without Libraries

Santaji GadeJavaScriptDevelopment3 weeks ago27 Views

vanilla javascript form validation

A step by step guide to a signup form validated with pure JavaScript functions, a debounced live email check, and ARIA accessible errors, backed by real Node and Chromium proof that invalid submits are blocked and valid ones succeed.

Development JavaScript Forms

Hi everyone, thanks for stopping by! This build tackles a page almost every site has and few build well: the humble signup form.

Ever hit submit on a form only to have it silently reject you with no idea what went wrong? Solid vanilla javascript form validation, no library, no framework, fixes that completely, and every claim below is backed by a real browser test, not just a description.

01

Why Vanilla JavaScript Form Validation Beats a Plugin

Most form plugins ship a fixed set of rules, a fixed error style, and a chunk of JavaScript a page has to download and parse before a single field can be checked. For four or five fields, that trade rarely makes sense.

Writing vanilla javascript form validation instead means every rule is written for the exact fields on the page, the error markup matches the site's own design system automatically, and there is no external dependency to update, patch, or eventually replace when it stops being maintained.

It also means full control over exactly when a field gets checked. A plugin often validates on every keystroke by default, which feels aggressive and error prone while someone is still mid word. A hand rolled approach can wait for a natural pause, covered in detail in the debouncing section below.

None of this requires advanced JavaScript. The techniques here are four small validator functions, one event listener per field, and one shared function that decides whether the whole form is allowed to actually submit.

Every browser feature used to build vanilla javascript form validation here, blur events, the input event, and the regex based checks, has had reliable support across all major browsers for years. caniuse's compatibility data confirms there is no meaningful risk of a visitor's browser silently failing to run any of it.

A signup form, a checkout form, and a newsletter form on the same site can all share the exact same four validator functions, with only the field wiring differing between them. Writing the logic once and reusing it everywhere keeps the whole site's forms behaving consistently, instead of each page reinventing slightly different rules.

02

Writing Reusable Validator Functions

The cleanest structure is a small set of pure functions, each one answering exactly one question about a value, with no dependency on the DOM at all. That makes them trivial to test on their own, and just as easy to reuse across a login form, a checkout form, or a newsletter signup.

validators.js
function isRequired(value) {
  return value.trim().length > 0;
}

function isValidEmail(value) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
}

function minLength(value, n) {
  return value.trim().length >= n;
}

function passwordsMatch(password, confirm) {
  return password === confirm && password.length > 0;
}

Each function takes a plain string and returns a plain boolean, nothing more. The real test below runs all four against eight genuine inputs, valid and invalid, and confirms every single result matches what it should.

Real Node output showing four validator functions run against 8 real inputs, all 8 checks correct
Real Node output: 8 real inputs run through the validator functions, every result correct.

A regex like the one inside isValidEmail will never catch every technically invalid email address, and that is fine. The Constraint Validation API documentation makes the same tradeoff: a reasonable pattern check on the client, backed by a real verification step on the server, like an actual confirmation email.

03

Debouncing Live Validation While Typing

Validating on every single keystroke means running a check, and potentially redrawing an error message, dozens of times while someone is still typing their email address. That is wasted work, and it can make an error message flicker in and out distractingly.

debounceValidate.js
let emailDebounceTimer = null;
emailEl.addEventListener('input', () => {
  clearTimeout(emailDebounceTimer);
  emailDebounceTimer = setTimeout(validateEmail, 300);
});

This is the same debounce pattern covered in depth in the debounce versus throttle guide, applied here to keep live validation from firing until typing actually pauses for 300 milliseconds.

TIP

Only debounce the fields where typing happens continuously, like email or a username. A checkbox or a select element has no meaningful "still typing" state, so validating those immediately on change is the better choice.

The real proof below types 8 real keystrokes into the email field, 60 milliseconds apart, the way an actual person types. Zero validation runs happen while typing is still in progress, and exactly one runs once the pause is long enough.

A 300 millisecond window works well for a text field like email, long enough to cover a natural typing rhythm but short enough that a genuine pause still feels instant to the person typing. A slower field, like one that triggers a network lookup, might reasonably use something closer to 500 or 600 milliseconds instead.

04

Blocking Submission Until Every Field Passes

Blur based and debounced live checks catch most problems early, but the final gate has to sit on the form's own submit event, since a field a visitor never actually touched still needs to be checked before anything is sent anywhere.

handleSubmit.js
form.addEventListener('submit', (e) => {
  e.preventDefault();
  const allValid = validateName() & validateEmail()
    & validatePassword() & validateConfirm();
  if (allValid) {
    submitFormForReal();
  }
});

This full vanilla javascript form validation pass runs every single validator again, regardless of which field last had focus, guaranteeing nothing slips through simply because a visitor tabbed past it without typing anything.

Real Chromium output proving invalid email detection, debounced validation, a blocked submit on mismatched passwords, and a successful submit once fixed
Real Chromium output: invalid email caught, debounce collapses 8 keystrokes to 1 real check, mismatched submit blocked, corrected submit accepted.
05

Making Validation Errors Accessible

A red border alone communicates nothing to a screen reader user, and a sighted user relying on peripheral vision can miss a small color change entirely. Every error needs to be announced, not just shown.

Setting aria-invalid="true" on the field and aria-describedby pointing at the visible error message, alongside a role="alert" on that message itself, covers both cases at once. The WAI ARIA Authoring Practices guide covers this exact pattern for form errors in detail.

DID YOU KNOW?

WebAIM's research on accessible form validation found that error messages placed immediately after their field, rather than grouped in a summary at the top, are located by screen reader users significantly faster.

The same role="alert" region also needs its text cleared, not just hidden, once a field becomes valid again, so a screen reader does not keep announcing an error that no longer applies.

Styling the error state matters too, not only the markup. CSS-Tricks' guide to form validation UX recommends pairing the color change with a visible icon and border thickness change, since color alone is unreliable for a visitor with a color vision deficiency, not just for someone using a screen reader.

06

The UX Payoff: What Real Validation Fixes

The real proof above already shows the mechanics working end to end: an invalid email caught immediately, a debounced check that only runs once typing actually pauses, and a submit that is reliably blocked or accepted based on the real state of every field.

Approach Feedback Timing Screen Reader Support
No client validationOnly after a full server round tripDepends entirely on the server response
Native HTML5 validation onlyImmediate, but styling is hard to customizeInconsistent across browsers
Vanilla javascript form validationImmediate, on blur and debounced typingFully controlled via ARIA attributes

Fast, correctly announced feedback also affects real interaction responsiveness, tied closely to Interaction to Next Paint, since a heavy validation library running unnecessary checks on every keystroke can noticeably slow down how quickly the page responds to typing.

The same fundamentals apply outside JavaScript too. The site's own guide to validating and sanitizing PHP form input covers the server side half of this exact problem, since client side checks like these are a UX improvement, never a substitute for validating the same data again once it reaches the server.

web.dev's guidance on form field UX makes a related point worth remembering while building any vanilla javascript form validation setup: a form that explains exactly what went wrong, in plain language next to the field itself, consistently completes at a higher rate than one that only shows a generic error at the top of the page.

07

Common Mistakes to Avoid

Most vanilla javascript form validation bugs trace back to one of these five gaps, each one easy to catch with a quick manual pass through the actual form.

A useful check before shipping any validation change is opening Chrome DevTools' accessibility panel and confirming every error message is actually exposed in the accessibility tree, not just visually present on the page.

  • Trusting client side validation alone: a request sent directly to the server, bypassing the page entirely, skips every JavaScript check completely, so the server must validate everything again.
  • Validating only on submit: a visitor gets no feedback until the very end, forcing them to hunt back through every field at once instead of fixing problems as they go.
  • Forgetting to clear a resolved error: a message that lingers after the field becomes valid reads as a bug and erodes trust in the rest of the form.
  • Skipping the debounce on a live field: validating on every keystroke can flicker distractingly and waste real processing time for no benefit, worth comparing against a library like validator.js to see how a mature project times its own checks.
  • No visible focus state on an invalid field: a keyboard user tabbing through the form has no way to tell which field the error actually belongs to without a clear outline or border change.

Frequently Asked Questions

Full control over rules, timing, and error styling, plus no external dependency to download, patch, or eventually replace, all for a page that usually only needs a handful of checks.

It handles basic cases like a required field or an email pattern, but its default error bubbles are hard to style consistently across browsers, which is why most production forms layer custom JavaScript on top.

Yes for most fields, since blur happens naturally once someone moves on. A field like email benefits from an additional debounced live check too, so an error can clear itself without waiting for another blur.

No. Client side checks only improve the experience for a real browser visitor; a request can always be sent directly to the server, so every field must be validated again once it arrives there.

A specific, actionable message beats a vague one, for example naming the missing requirement directly rather than just labeling the whole password as invalid.

The validator functions themselves are framework agnostic pure functions, so they transfer directly. Only the event wiring and error rendering around them would need to change to match the framework's own patterns.

What We Learn Today

1

Validator functions should stay pure

No DOM access, just a value in, a boolean out.

2

Debounce live typing checks

Real proof: 8 keystrokes, only 1 real validation run.

3

Submit must recheck every field

Not just the one that last had focus.

4

Errors need an ARIA invalid state

A color change alone reaches no screen reader.

5

Client checks never replace server checks

A direct request bypasses all client JavaScript.

6

Clear resolved errors, don't just hide them

A lingering message reads as a bug.

Ready to Ditch the Validation Library?

This vanilla javascript form validation setup is a real, working starting point, pure validator functions, a debounced live check, and full ARIA support, all proven with real captured output above.

0 Votes: 0 Upvotes, 0 Downvotes (0 Points)

Leave a reply

Loading Next Post...
Search
Popular Now
Loading

Signing-in 3 seconds...

Signing-up 3 seconds...