A client's contact form was generating a steady stream of incomplete submissions, empty email fields, one-word messages, a phone number field with someone's name typed into it by mistake, that sort of thing. The PHP handler already validated everything server-side, rejecting anything malformed before it reached the notification email, but the round trip meant the visitor didn't find out until after the page reloaded, usually to a fairly plain error message that didn't explain much.
Where the friction was actually coming from
Watching a few people fill out the form in person, informally, over someone's shoulder, not a proper usability study, made the actual problem obvious: people were submitting, seeing nothing happen for a second while the page reloaded, then landing on an error page that told them something was wrong without saying what, at which point a meaningful number of them just left instead of scrolling back up to find the actual field that needed fixing.
Server-side validation was doing its job, protecting the database and the notification system from garbage data, but it was doing that job too late in the experience to actually help the visitor succeed.
What we added
Adding a plain JavaScript check on the submit event, no jQuery even needed for something this small, let us show an inline message immediately and stop the submit before it left the page. Server-side validation stays as the real gatekeeper, since JavaScript validation can always be bypassed or simply fail to load, this is just a better first impression for the overwhelming majority of visitors who do have JavaScript enabled.
- Bind to the form's submit event and call event.preventDefault() if any check fails.
- Check .value.length on required fields rather than a more elaborate "is this field touched" state machine, which is more than a simple contact form needs.
- A simple regex for the email field, nothing that tries to be a fully RFC-compliant email validator, just enough to catch "missing @ symbol" and "no domain at all," the two mistakes that actually show up in practice.
- Insert an inline error message directly after the offending field rather than a single summary at the top of the form, so the visitor doesn't have to hunt for which field is the problem.
- Clear that error message as soon as the visitor starts correcting the field, rather than leaving it up until the next submit attempt.
Why we didn't reach for a validation library
There are jQuery plugins built specifically for form validation, some fairly sophisticated ones with configurable rule sets and internationalized error messages. For this specific form, four fields, two of which just need a length check, we didn't need any of that. The whole thing came in under forty lines of plain JavaScript, and every line of it is something either of us can read and modify in thirty seconds without consulting a plugin's documentation first.
That calculus would change on a longer, more complex form, a multi-step signup wizard with conditional fields, say, where a proper validation library's rule engine would save real time. Matching the amount of tooling to the actual complexity of the form has been a theme across a lot of our decisions this year, not just this one.
The regex, and why we kept it deliberately loose
Our email regex is intentionally forgiving. It's tempting to write something that tries to validate every technically-invalid email format, but the more precise you try to make an email regex, the more likely you are to reject a real, valid email address that just has an unusual but legal format. We settled on checking for an @ symbol with at least one character before it and a dot somewhere after it, which catches the overwhelming majority of genuine typos, hitting caps lock and typing "clientexample.com," forgetting the @ entirely, without risking a false rejection of something valid.
Server-side, we're slightly stricter, but that's a defense-in-depth choice rather than something we'd want to burden the client-side experience with, since a stricter check client-side just means more false rejections for real visitors typing real, if unusual, email addresses.
What happens with JavaScript disabled
A small but real slice of visitors, by our own admittedly rough server logs, somewhere around two or three percent, either have JavaScript disabled or are running some kind of script blocker that catches inline event handlers. For those visitors, nothing changes from before, the form submits normally, hits the same PHP validation that was already there, and they see the same server-rendered error page as always. We were careful not to make the JavaScript enhancement a requirement for the form to function at all, which meant building the validation as an addition to the existing submit flow rather than replacing it with something that assumes script availability.
This is the part of "progressive enhancement" that's easy to nod along with in the abstract and easy to accidentally violate in practice, it would have been simpler to just intercept the submit unconditionally and build the actual submission as an AJAX call handled entirely in JavaScript, but that trades a small percentage of visitors losing the form outright for a marginally smoother experience for everyone else, a bad trade for a plain contact form where reliability matters more than polish.
A small accessibility note
Inline error messages need to actually be perceivable by someone using a screen reader, not just visually obvious to a sighted visitor glancing at red text near a field. We made sure the error text is in the DOM as an actual sibling element with a clear, readable sentence, rather than a CSS-only visual treatment like a red border with no accompanying text, which would leave a screen reader user with no indication anything is wrong beyond a form that silently didn't submit.
We haven't gone further than that, no aria-live region announcing the error dynamically, no focus management moving the visitor's cursor to the first invalid field, both of which would be genuine improvements. Filed under things worth coming back to once this pattern is used widely enough to justify polishing it further, rather than blocking rolling it out now while we get those details exactly right.
Testing across the browsers that actually matter here
Since nothing like native browser form validation exists yet in the browsers our clients' visitors use, we tested the inline validation across every browser showing up meaningfully in this client's analytics, several versions of Internet Explorer included, rather than just the modern browser one of us happens to develop in day to day. IE's event handling has enough quirks around addEventListener versus attachEvent that we ended up leaning on jQuery for the actual event binding after all, even though the validation logic itself stayed plain JavaScript, since getting cross-browser event binding exactly right by hand wasn't worth reinventing when jQuery already solves it reliably.
That's a small concession against the "no jQuery needed for something this small" framing, but it's a pragmatic one. jQuery is already loaded on the page for other things, so using it just for reliable event binding costs nothing extra, while hand-rolling cross-browser event handling ourselves would have added real risk for no real benefit.
What we'd tell someone building this for the first time
Start with the two or three validation rules that actually matter, required-field and a loose email format check covers the overwhelming majority of real submission problems, rather than trying to anticipate every conceivable bad input up front. It's tempting to build a comprehensive validation framework the first time this problem comes up, matching field types, custom rule callbacks, configurable messages, but a plain contact form rarely needs any of that, and the extra structure mostly just delays shipping the fix that actually reduces junk submissions. We'd rather ship the simple version this week and revisit it if a future form's requirements genuinely outgrow it, than spend that same week building flexibility nothing has asked for yet.
Nothing fancy, just checking .value.length and a simple regex for the email field, but the drop in junk submissions was immediate. We went from roughly a third of submissions missing something basic to a small fraction of that, mostly now people who have JavaScript disabled entirely and still hit the server-side rejection, which is exactly the case that layer exists for. We're adding this same lightweight pattern to two other client forms this month, a newsletter signup and a quote request form, since the fix is small enough to justify rolling out broadly rather than treating it as a one-off fix for a single client's complaint.