Forms with missing labels, wrong input types, no autocomplete, or inaccessible error messages fail HTML validation and fail real users. The fixes are small but compound across every form on your site — checkout, signup, contact, comment. This guide covers label association, the right input types, autocomplete tokens, native HTML5 validation, and accessible error patterns. For related fixes, see the HTML Checker Fixes index.
<label> Email <input type="email" name="email" required> </label>
<label for="email">Email</label> <input id="email" type="email" name="email" required>
placeholder="Email" disappears when the user starts typing, removing context. Some screen readers ignore placeholder entirely. Always use a real label.type="email" - email validation + email keyboard on mobile type="tel" - numeric keyboard on mobile type="url" - URL keyboard, .com key on mobile type="number" - numeric input with up/down spinners type="date" - native date picker type="time" - native time picker type="search" - search-styled input with clear button type="password" - obscured input
Using type="text" for every field is a missed opportunity. The right type enables: browser-native validation, mobile keyboard optimisation, autofill behaviour, accessibility tools.
Browsers and password managers use autocomplete tokens to fill correctly. Without them, autofill is unreliable.
<input type="email" name="email" autocomplete="email"> <input type="text" name="given_name" autocomplete="given-name"> <input type="text" name="family_name" autocomplete="family-name"> <input type="tel" name="phone" autocomplete="tel"> <input type="text" name="street_address" autocomplete="street-address"> <input type="text" name="postcode" autocomplete="postal-code"> <input type="password" name="password" autocomplete="current-password"> <input type="password" name="new_password" autocomplete="new-password">
Full token list at MDN.
autocomplete="new-password" on sign-up forms tells password managers to suggest a strong password. autocomplete="current-password" on login tells them to fill the existing one. Distinct tokens for distinct intent.<input type="email" required minlength="3">
<input type="number" min="0" max="100" step="1">
<input type="text" pattern="[A-Z]{2}[0-9]{4}" title="2 letters then 4 digits">
The browser handles validation, shows tooltips, and blocks submission of invalid forms — all without JavaScript.
<label for="email">Email</label>
<input id="email" type="email"
aria-invalid="true"
aria-describedby="email-error">
<span id="email-error" role="alert">
Please enter a valid email address.
</span>
aria-describedby links the error to the field; aria-invalid marks the field as invalid; role="alert" announces the error to screen readers when it appears.
const email = document.querySelector('input[type=email]');
email.addEventListener('blur', () => {
if (!email.checkValidity()) {
email.setAttribute('aria-invalid', 'true');
document.getElementById('email-error').textContent = email.validationMessage;
} else {
email.removeAttribute('aria-invalid');
document.getElementById('email-error').textContent = '';
}
});
checkValidity() uses the same rules HTML5 native validation applies. Build on it instead of replacing it.
Re-run the HTML Checker after fixes. Test each form: