Give every field a visible label, add the right autocomplete token and input type, make
error and confirmation messages announce themselves, and make the button say what will happen. A console
script can check those on any form; on a typical placeholder-only form it found 15 problems.
Key takeaways
- Placeholders are not labels. They vanish when you type, and WCAG 1.3.1 and 3.3.2 ask for labels or instructions that persist.
- Autocomplete saves typing and is a WCAG requirement. Success criterion 1.3.5 asks for the input purpose to be identifiable for common personal-data fields.
- The right input type opens the right keyboard.
type="email"andtype="tel"help on phones. - Errors need identifying and describing in text. WCAG 3.3.1 asks for the field in error to be identified and the error described in text, and 4.1.3 for status messages to be exposed to assistive technology.
- Ask for less. Every extra field is a reason to leave; the form that needs a name, a way to reply and a message should ask for exactly those.
The rules behind the audit
| Check | Why | Status |
|---|---|---|
| Every field has a label | 1.3.1 Info and Relationships, 3.3.2 Labels or Instructions, 4.1.2 Name, Role, Value | Documented |
| Autocomplete on name, email, phone, address | 1.3.5 Identify Input Purpose | Documented |
| Errors identified and described in text | 3.3.1 Error Identification | Documented |
| Messages announced without moving focus | 4.1.3 Status Messages | Documented |
| Targets at least 24 by 24 CSS pixels (or spaced) | 2.5.8 Target Size (Minimum) | Documented |
| Button says what happens ("Send message") | Clarity, not a WCAG requirement | Practice |
| Email and phone use the matching input type | Mobile keyboards, built-in validation | Practice |
Source: WCAG 2.2.
The audit script
Paste it in the browser console on the page, then run auditForms():
function auditForms() {
const HINTS = { name: 'name', fullname: 'name', email: 'email', tel: 'tel', phone: 'tel', address: 'street-address', postcode: 'postal-code', zip: 'postal-code', city: 'address-level2', org: 'organization', company: 'organization' };
const report = [];
document.querySelectorAll('form').forEach((form, fi) => {
const issues = [];
form.querySelectorAll('input, select, textarea').forEach(el => {
if (['hidden', 'submit', 'button', 'checkbox', 'radio'].includes(el.type)) return;
const id = el.name || el.id || el.type;
const labelled = el.labels && el.labels.length || el.getAttribute('aria-label') || el.getAttribute('aria-labelledby');
if (!labelled) issues.push(id + ': no label' + (el.placeholder ? ' (placeholder only)' : ''));
const key = Object.keys(HINTS).find(k => (el.name + ' ' + el.id).toLowerCase().includes(k));
if (key && !el.autocomplete) issues.push(id + ': add autocomplete="' + HINTS[key] + '"');
if (/email/i.test(el.name + el.id) && el.type !== 'email') issues.push(id + ': use type="email"');
if (/(tel|phone)/i.test(el.name + el.id) && el.type !== 'tel') issues.push(id + ': use type="tel"');
const h = el.getBoundingClientRect().height; if (h && h < 24) issues.push(id + ': ' + Math.round(h) + 'px tall (under 24px)');
});
form.querySelectorAll('button, input[type=submit]').forEach(b => {
const t = (b.textContent || b.value).trim();
if (/^(submit|send|go|ok)$/i.test(t)) issues.push('button "' + t + '": say what happens');
const r = b.getBoundingClientRect(); if (r.height && r.height < 24) issues.push('button "' + t + '": ' + Math.round(r.height) + 'px tall');
});
if (!form.querySelector('[aria-live], [role=alert], [role=status]') && !form.parentElement.querySelector('[aria-live], [role=alert], [role=status]')) issues.push('no live region: errors and confirmations may not be announced');
report.push({ form: fi + 1, id: form.id || form.getAttribute('data-signup') !== null && 'data-signup' || '(none)', issues });
});
return report;
}
Results
On a deliberately typical bad contact form (placeholder-only name, email, phone and message fields, a button labelled "Submit", no message area) it reported 15 issues: four missing labels, three missing autocomplete tokens, two wrong input types, four undersized targets, a vague button and no live region.
On this site's forms it found one real problem. The newsletter form's confirmation message was not in a live
region, so a screen reader would not announce success or failure. I added role="status" to it
on every page, and the audit now reports no issues for it. The audit also flagged the three range sliders in
the hosting calculator as 5 px tall, which is a false alarm: the slider track is thin by design and the
draggable handle is larger. I mention it because scripts like this overreport and you need to read each line.
Beyond the audit
- Keep the visible label above the field, not inside it; it stays readable while typing.
- Validate on submit, not on every keystroke, and put the message beside the field and in a summary the screen reader can reach.
- Keep what the user typed when an error occurs.
- Say what happens next after sending: when they will hear back, and where to go if they do not.
- Test it with the keyboard alone, which is part of the 10-minute accessibility test.