A Beginner's Guide to UK GDPR for Web Developers: Cookies, Consent and Data Requests
October 07, 2026 - Web Development
A contact form carries more weight than it looks. When it works, nobody notices. When it fails, you lose the message and the person who sent it. Building one that loads quickly, explains itself to assistive technology and survives a keyboard-only visitor is less about clever code and more about restraint. The approach here is straightforward: write semantic HTML that works on its own, style it without breaking focus, then add validation in vanilla JavaScript as an enhancement rather than a crutch.
Before any scripting, the form should submit and be readable by a screen reader. Get the markup right first and everything after it becomes easier.
Keep the native constraints too: required, type="email", maxlength and a pattern where it genuinely helps. Without JavaScript, the browser enforces them, and you have a working baseline.
Once your script loads, call form.noValidate = true (or set the novalidate attribute) so your own messages take over. Otherwise visitors see two rounds of complaints: the browser bubble and then yours, which feels broken.
Placeholder text is not a label. It disappears the moment someone starts typing, and it is often the only clue about what a field wants. Put a visible label above each input, then use the placeholder, if at all, for an example like "[email protected]".
Build error messages into the markup as empty elements with unique ids, and point to them with aria-describedby on the input. An input might describe both a hint and an error: aria-describedby="email-hint email-error". Leave the error element empty until it is needed, then fill it with a short sentence. "Enter an email address in the format [email protected]" tells someone what to do. "Invalid input" does not.
If you group radio buttons or checkboxes, wrap them in a fieldset with a legend. Screen readers announce the legend with every option inside, which is exactly what you want.
The stylesheet should stay quiet. A single column, a max-width around 60 characters, and grid or flexbox with a consistent gap handles the layout for almost any form.
Keep any transitions brief and wrap decorative movement in a prefers-reduced-motion query. A form is not the place for animation that competes with the task.
Attach one submit handler, prevent the default, and walk through the fields in order. Collect the first invalid control as you go, write your messages into the error slots, set aria-invalid="true" on anything that fails, and move focus to the first problem field at the end. That single focus move saves keyboard users a hunt through the page.
Keep the rules small and obvious: is it empty, is the format plausible, is the message long enough to be useful. For email, a light check for an @ symbol and a dot in the domain is plenty. Elaborate regular expressions reject valid addresses and rarely catch the invalid ones; the real test is whether the message arrives.
Checking a field the moment someone types the first character is hostile. Validate when focus leaves the field, and clear the error as soon as the visitor starts correcting it. Once a field has failed, re-check it on input so feedback feels immediate. Never validate an empty field on blur if the visitor is simply tabbing past it on the way to somewhere else — wait for a submit attempt before complaining about missing values.
Send the form with FormData and fetch, and check response.ok before assuming success. Disable the button while the request is in flight and change its text to something like "Sending…", then use a flag to guard against a double submit from an impatient click. If the request fails, keep the visitor's text in the fields and show a message that says what happened and what to do next.
On success, reset the form, write a confirmation into a live region, and move focus there so the announcement is not missed. A polite status region is usually the right choice; the message is not urgent enough to interrupt.
If the form already works without JavaScript, everything you add afterwards is a bonus rather than a dependency.
The finished form is small: a few dozen lines of markup, a stylesheet that fits on one screen, and validation logic you can read in a sitting. That is the point. Every extra library, animation and clever pattern adds weight to the page and another thing to maintain, while the visitor only wants to type a sentence and press a button.
Ship it, then come back after a month and read the messages that actually arrived. Real submissions tell you which fields confuse people far better than any amount of guessing at a desk.
Photo: viarami / Pixabay
Copyright © 2026 Wren Stack. All Right Reserved.