Inline error messages
An error message that nobody finds is just decoration. Wire it to the input, mark it invalid, and put focus where the user can act.
When a form fails to submit, the user gets two questions in quick succession: "Which field is wrong?" and "What do I do about it?" If your error message answers neither — or answers them only on screen, only on submit, only with a red border — you've shipped a dead-end. We have two ARIA attributes purpose-built for this, and one focus rule that decides whether the user even notices.
This is the lesson where ARIA actually earns its keep. The label work from Lesson 1 is now load-bearing: every wire we run goes back to that accessible name.
aria-invalid: marking the field
aria-invalid="true" flags an input as currently in an error state. Screen readers announce "invalid" alongside the field's name. CSS can target [aria-invalid="true"] for the red border. Default value is false, so flip it on only when the field is actually wrong.
<label for="email">Email</label> <input id="email" name="email" type="email" aria-invalid="true" />
Common mistake: leaving aria-invalid="true" on a field that's been corrected. Toggle it back to "false" (or remove it) the moment the value passes validation again. Stale invalid states make the form sound permanently broken.
Don't set aria-invalid="true" the second the input gets focus — only after the user has had a chance to type something and you've actually validated. Validating empty fields on focus is hostile.
aria-describedby: linking the message
aria-invalid says something is wrong. aria-describedby says here's what's wrong, read it. Its value is the id of the element holding the message. Screen readers read the description right after the field's name and required/invalid status.
<label for="email">Email</label> <input id="email" name="email" type="email" aria-invalid="true" aria-describedby="email-error" /> <p id="email-error" class="error"> Enter an email like name@example.com </p>
A screen reader hears "Email, edit, invalid entry, Enter an email like name at example dot com." That's everything they need without leaving the field.
aria-describedby accepts a space-separated list of ids, so you can chain a hint and an error:
<input id="password" type="password" aria-describedby="pw-hint pw-error" aria-invalid="true" /> <p id="pw-hint">At least 12 characters.</p> <p id="pw-error">Too short — you've entered 7.</p>
Keep the message a short sentence. Screen readers read it in full every time the user re-enters the field, so verbosity gets old fast.
Where the message goes
Two patterns are common:
Inline, directly below the field. Best for most forms. The error sits in the visual flow, right where the user's eyes are.
Inline + summary at top. For long forms — Lesson 4 — you also list every error at the top with anchors. The inline messages still exist; the summary is a sighted-user shortcut.
What you should not do: put the error in a tooltip that appears on hover, in a popup that disappears, or in a toast that flies in from the corner. Errors are persistent. They live next to the field until fixed.
Focus on error: where the cursor goes after submit
When the user submits and validation fails, someone has to drive the cursor to the problem. Browsers do this for native required constraints; for any custom or async validation, you do it in JS.
Two strategies:
- Focus the first invalid field. Programmatically
.focus()it. Screen reader announces the field name, invalid, and the error. User starts typing. - Focus an error summary at the top. Covered in Lesson 4. Best for long forms with many errors.
What you should not do is just slap red borders on six fields and let the user hunt. Sighted users can scan; screen reader users cannot.
form.addEventListener("submit", (e) => {
const firstInvalid = form.querySelector("[aria-invalid='true']");
if (firstInvalid) {
e.preventDefault();
firstInvalid.focus();
}
});Don't move focus while the user is mid-typing in another field. Focus-on-error fires after submit, not on every keystroke. Lesson 5 covers the live-validation case.
What the message should say
The message is the smallest thing on the page and it does the most work. Three rules:
- Be specific. "Enter a valid email" is worse than "Enter an email like name@example.com."
- Say what to do, not what went wrong. "Too short — needs 12 characters" beats "Invalid password."
- Don't shame. "Whoops! Looks like you forgot something silly!" gets old at 11pm with a deadline. Just tell them what's missing.
The message should make sense out of context. Screen reader users hear it without seeing the visual layout, so "this field is wrong" is meaningless — "Enter your email" always works.