Product design skills

forms.md

A supporting file of the better-accessibility skill.

Forms

Labels, autocomplete, error messaging, input types and submit behavior.

Labels

Every control needs a programmatic label: <label for> pointing at the input's id, or a wrapping <label>. A placeholder is never a label. It disappears the moment the user types and usually fails contrast.

<!-- Good: explicit association -->
<label for="email">Email</label>
<input id="email" type="email" autocomplete="email" />

<!-- Good: wrapping label, so label and control share one hit target -->
<label>
  <input type="checkbox" /> Send me updates
</label>

Label and control share one hit target, so clicking "Send me updates" toggles the checkbox with no dead zone between them. Mark required fields with native required plus a visible indicator explained once per form ("* required").

A placeholder used in addition to a label shows an example of the expected format: placeholder="name@company.com".

Error messaging

The complete pattern:

<label for="email">Email</label>
<input
  id="email"
  type="email"
  autocomplete="email"
  aria-invalid="true"
  aria-describedby="email-error"
/>
<p id="email-error">Enter a valid email address.</p>
  • aria-invalid="true" on the failing field, removed once fixed.
  • aria-describedby links the field to its inline error so screen readers announce it with the field.
  • Errors render inline beside their fields, with an icon or text. Never a red border alone, which is a color-only cue.
  • On submit, focus the first invalid field.
  • Allow incomplete submission so validation can surface. Never disable submit until valid (see below).
  • Accept free text and validate after. Never block typing or filter characters as the user types. Trim values before validating, because autocomplete and text expansion add trailing spaces.

Autocomplete and input types

autocomplete with a meaningful name fills forms in one tap and is a WCAG requirement (1.3.5) for fields about the user. The common tokens:

Fieldautocomplete
Namename (or given-name / family-name)
Emailemail
Phonetel
Addressstreet-address, address-line1, postal-code, country
Cardcc-number, cc-exp, cc-csc, cc-name
Loginusername, current-password
Signup / resetnew-password
2FA codeone-time-code

Prefix with a section where relevant: autocomplete="shipping street-address".

Correct type and inputmode pick the right mobile keyboard:

InputUse
Email, URL, phonetype="email", type="url", type="tel"
OTP / PIN / card numbertype="text" inputmode="numeric" (keeps text semantics, no spinner)
Money, decimalstype="text" inputmode="decimal"
True numeric quantitytype="number"

Disable spellcheck on emails, codes and usernames: spellcheck="false".

Never fight the user's tools

  • Stay compatible with password managers and 2FA autofill: real <form>, correct autocomplete, no fake inputs.

Submit behavior

  • Keep submit enabled until the request starts, then disable it and show a spinner beside the original label. "Save" with a spinner, not a bare spinner. The label is what tells assistive tech which button is busy.
  • Announce results. Success goes through a polite live region. On failure, focus the first invalid field, which is itself the announcement. Reserve role="alert" for form-level errors not tied to a field (screen-readers.md).
  • Warn on unsaved changes before navigation, and never lose typed input to a re-render. Hydration must preserve focus and value.
  • Enter submits from any focused input; in <textarea>, ⌘/Ctrl+Enter submits.

On this page