⌨️ Web development

Accessible and secure HTML forms: validation and good practice

How to build HTML forms everyone can use: labels, input types, autocomplete, error messages, validation in the browser and on the server, and spam defence.

Forms are where a website stops being just something to read and asks the user for something: a contact, a purchase, a sign-up. They are also where most users drop off, because of confusing fields, incomprehensible errors or the wrong keyboard on their phone. And they are an entry point for untrusted data. A good form has to be clear, accessible and robust at the same time.

Every field has its label

A placeholder is not a label. It disappears as soon as the user starts typing, often has low contrast and is not always read by assistive technologies. Every field needs a linked label:

<label for="email">Email address</label>
<input id="email" name="email" type="email" autocomplete="email" required>

The for attribute must match the field's id. The link also has a practical benefit: tapping the label activates the field, which is useful for small checkboxes and radio buttons.

For groups of related options use fieldset and legend:

<fieldset>
  <legend>How would you prefer to be contacted?</legend>
  <label><input type="radio" name="contact" value="email"> Email</label>
  <label><input type="radio" name="contact" value="phone"> Phone</label>
</fieldset>

The right input type

The type attribute is not a cosmetic detail. On mobile devices it changes the keyboard, and in every browser it enables native checks:

Type Main effect
email keyboard with @, format check
tel telephone keypad
url keyboard with / and .com, format check
number numeric values with spinner arrows
date native date picker
search search field with a clear button

Be careful with number: it is meant for quantities, not codes. For postcodes, card numbers or verification codes use type="text" with inputmode="numeric", which shows the keypad without adding arrows and without stripping leading zeros.

Autocomplete: less typing, fewer mistakes

The autocomplete attribute tells the browser what kind of data is being requested, so it can offer saved values. Values such as name, email, tel, street-address, postal-code, country, cc-number, new-password and current-password are standardised.

Using them correctly saves everyone time and is particularly helpful for people with motor impairments. For password fields, new-password lets password managers suggest a strong password, while current-password makes signing in easier.

Validation in the browser

HTML offers native validation with attributes such as required, minlength, maxlength, min, max and pattern:

<label for="zip">Postcode</label>
<input id="zip" name="zip" inputmode="numeric"
       pattern="[0-9]{5}" required
       aria-describedby="zip-help">
<p id="zip-help">Five digits, for example 95100.</p>

You can style invalid fields with the :user-invalid pseudo-class, which applies only after the user has interacted with the field, so a freshly opened form is not covered in red errors.

Useful error messages

A good error message says what is wrong and how to fix it. "Invalid field" does not help; "The date must be after today" does. Other tips:

  • show the error next to the field, linked through aria-describedby;
  • mark the field with aria-invalid="true";
  • do not rely on colour alone: add text or an icon with alternative text;
  • after submission, if there are errors, move focus to the first invalid field or to a summary at the top of the form;
  • do not wipe the data already entered when the form is rejected.

Browser validation is not security

Everything that happens in the browser can be bypassed: anyone can edit the HTML, disable JavaScript or send a request straight to the server. Client-side validation is for usability; security requires repeating every check on the server.

On the server:

  • check the type, length and format of every field, rejecting anything unexpected;
  • use parameterised database queries, never string concatenation;
  • encode data when you display it on a page, to prevent cross-site scripting;
  • for file uploads, check size and actual type and store files outside the public folder, with server-generated names;
  • protect forms that change data with anti-CSRF tokens or suitable SameSite cookies.

Fighting spam without punishing users

Public forms attract automated submissions. Before turning to a CAPTCHA, which weighs on accessibility and experience, try less intrusive measures:

  • a "honeypot" field hidden from users with CSS, which bots tend to fill in: if it has a value, the submission is discarded;
  • a check on completion time: a form filled in under a second is unlikely to be human;
  • rate limits per address or per session on the server.
<div class="hidden" aria-hidden="true">
  <label for="website">Leave this field empty</label>
  <input id="website" name="website" tabindex="-1" autocomplete="off">
</div>

tabindex="-1" and aria-hidden stop keyboard and screen reader users from landing in the honeypot field by mistake.

Ask only for what you need

Every extra field lowers the chance that the form gets completed and increases the personal data you have to look after. If a piece of information is not essential, remove it or make it optional, and clearly indicate which fields are required.

In short

  • Every field has a linked label; a placeholder does not replace it.
  • Use the correct type and autocomplete for suitable keyboards and quick filling.
  • Write specific error messages, linked to the field and not based on colour alone.
  • Browser validation helps the user; security happens on the server.
  • Against spam, try honeypot fields and rate limits before CAPTCHAs.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. A computer science graduate, he combines rigorous technical analysis with attention to the people who use technology.

Read also