Forms, content, and perception

Label and group form controls

Give every control a persistent name, group related choices, and provide instructions before they are needed.

Every form control needs a name that stays visible while the user types. Related controls need a group name, so a single radio button makes sense on its own. And instructions have to arrive before the user needs them, not after they got it wrong. WCAG covers this in 1.3.1 Info and Relationships and 3.3.2 Labels or Instructions, both Level A.

The flawed registration form fails all three. Let’s fix it field by field.

Placeholders are not labels

The name and email fields rely on placeholders:

<input type="text" placeholder="Full name">
<input type="text" placeholder="Value">

A placeholder disappears the moment you type. Come back to the field to fix a typo and you no longer know what it asks for. The second one says “Value”, which is what happens when a component ships with a default nobody changes. And placeholder text is usually too light to pass contrast.

Give each control a real label, linked by for and id:

<label for="fullname">Full name</label>
<input id="fullname" name="fullname" type="text" autocomplete="name">

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

Check it in the Chrome Accessibility pane. The email input now shows Name: "Email" from label for. Clicking the label also focuses the field, which makes the tap target bigger on phones for free.

Tell the browser what the field is

The autocomplete attributes are not decoration. name and email are input purpose tokens from the HTML spec. The browser uses them to offer the right autofill entry, and a user with a cognitive disability or a motor impairment fills the form with one tap instead of typing. This is WCAG 1.3.5 Identify Input Purpose, Level AA.

For the address block use street-address, postal-code and country-name. Focus the field in Chrome, and the autofill dropdown shows your saved address instead of a list of random past entries. That dropdown is the proof it worked.

The contact preference radios sit under a div that says “Contact preference”. Visually it’s a group. To a screen reader each radio is an orphan: “Email, radio button, 1 of 2”. One of two what?

Wrap them in a fieldset with a legend:

<fieldset>
  <legend>Contact preference</legend>
  <label><input type="radio" name="pref" value="email"> Email</label>
  <label><input type="radio" name="pref" value="phone"> Phone</label>
</fieldset>

When focus enters the group, VoiceOver announces “Contact preference, group” and then “Email, radio button, 1 of 2”. The question travels with the answer.

Instructions before, not after

The arrival day field accepts only the two event days. The flawed page tells you after you submit. Put the hint next to the field and link it with aria-describedby:

<label for="arrival">Arrival day</label>
<input type="date" id="arrival" name="arrival" aria-describedby="arrival-hint">
<p id="arrival-hint">14 or 15 October 2026</p>

The field is announced as “Arrival day, date field, 14 or 15 October 2026”. The user knows the constraint before touching the keyboard.

Try this on your own form: Tab through every field with a screen reader running and write down each one where the announced name is missing, vague, or different from the visible text.

Lesson completed