Lesson · Beginner
Accessible HTML forms
Build a form with persistent labels, sensible input types, grouped controls, instructions, and honest submission behavior.
- Published
- Last checked
Labels are part of the control
Placeholder text is not a persistent label. Associate visible text through
nesting or matching for and id values.
<form action="/contact" method="post">
<div>
<label for="email">Email address</label>
<input id="email" name="email" type="email" autocomplete="email" required />
</div>
<div>
<label for="message">How can we help?</label>
<textarea id="message" name="message" rows="6" required></textarea>
</div>
<button type="submit">Send message</button>
</form>
This markup describes a request, but it does not create a server. The action
must point to a real endpoint before you claim the message is sent.
Error handling
Browser validation is a useful baseline. A production endpoint must validate again on the server, return useful errors, preserve safe input, and move or announce focus appropriately.
How browsers actually behave
On submission, the browser constructs name-value pairs from successful controls
and sends them according to the form method and encoding. A control without a
name is not submitted. A button's default type inside a form is submit, so
utility buttons need an explicit type="button".
Input types can provide keyboards, parsing, autofill hints, and native constraint validation. They do not prove the value is truthful or safe. The server must validate every field, enforce authorization, and encode output even when the browser blocked an obviously bad format.
Decisions and trade-offs
Use GET for safe, repeatable queries whose parameters may appear in the URL; use POST for state changes and data that should not become a bookmark or history entry. Neither method encrypts data—HTTPS does. Collect only fields needed for the stated purpose and explain what happens after submission.
Native controls are the baseline. Custom comboboxes, date pickers, and multi-select widgets are justified only when documented requirements exceed native behavior and the full keyboard, focus, naming, and error contract can be maintained.
Accessibility consequences
Labels stay visible while a person types and enlarge the clickable target.
fieldset and legend name groups such as delivery choices. Instructions
belong before the relevant interaction, and error messages need both a visible
location and a programmatic relationship.
Autofill tokens help people complete common personal fields. Do not disable paste in password or confirmation fields. At high zoom, place labels above controls when side-by-side layouts would compress or separate them.
Common failures and fixes
- The server receives an empty value. The control has no
name, is disabled, or is outside the form. Inspect the submitted request. - A cancel button submits. Its type was omitted. Set
type="button". - A placeholder is the only label. It vanishes during input. Add a persistent associated label.
- Client validation passes but unsafe data reaches output. Format checking was mistaken for security. Validate and encode on the server.
- Success appears before delivery. UI state is not tied to the endpoint. Confirm only after a verified response and offer a retry path.
Verify it
Inspect the request payload in DevTools and confirm each intended field has the right name and no unnecessary data. Submit with JavaScript disabled, with empty required fields, malformed values, slow network, server rejection, and a successful response. Complete every path by keyboard and inspect control names, descriptions, and invalid states. Test 320-pixel reflow, 200% zoom, autofill, paste, and preservation of safe values after errors.
Exercise and next step
Add a grouped set of contact preferences using fieldset and legend. Try the
form with only a keyboard and confirm the visible label remains available while
typing. Next, place the form inside a page whose landmarks and headings remain
coherent without CSS.
Boundary of this lesson
HTML defines the controls and submission contract; it does not provide storage, email delivery, payment processing, abuse prevention, or consent by itself. Those capabilities require a real server boundary, documented data handling, monitoring, and recovery. Do not publish decorative success behavior while the endpoint is absent. When a third-party processor is involved, identify what data leaves the site, keep the collected fields minimal, and verify its actual test-mode and failure behavior before enabling the production path.
Practice with the request boundary
Submit the same form through native navigation and an enhanced fetch path. Capture both requests and compare method, URL, encoding, names, values, credentials, and server response. Introduce a timeout and a server-side validation error after several valid fields. The user-facing result, retained safe values, and authoritative data should agree across both paths. Any difference is a contract decision that needs documentation and a regression test.
Repeat the comparison with autofill, pasted text, a restored browser-history entry, and a duplicate submission attempt. The visible controls, encoded request, server decision, and recovery copy should remain consistent. This final pass catches state assumptions that are invisible when every test starts with an empty form and a fast response.
Frequently asked questions
Does HTML form validation replace server validation?
No. Browser validation improves immediate feedback, but the server must validate every submitted value, enforce authorization and business rules, and encode untrusted output.
Should placeholder text be used as a form label?
No. A persistent visible label stays available while a person types and provides a reliable accessible name. Use placeholder text only for an optional example or short hint.
Free template + launch checklist
Build a page you can check before launch
Start with one reviewed semantic HTML page, then work through the one-page checklist against your published URL.