How do you get an error message to a screen reader at the right moment?
On this page
An error reaches a screen reader at the right moment when it is programmatically tied to the field it belongs to and announced through a live region the instant it appears. The work is two-fold: associate the message with the input so a user who navigates to that field hears the error as part of it, and signal the change so the announcement happens at the moment of failure rather than waiting for the user to stumble across red text. A visual-only error, a field turned red with a message painted beside it, is simply invisible to assistive technology. Color and position communicate nothing to a screen reader; only structure and live regions do.
The association half makes the error part of the field’s identity. You connect the message to the input with aria-describedby pointing at the element that holds the error text, and you set aria-invalid="true" on the field so its state is announced as invalid. Now when a screen reader user moves focus to that field, they hear its label, that it is invalid, and the description of what went wrong, all together. Without this link, the error text is just an orphaned string somewhere in the DOM that the user has no reason to visit and no way to connect to the control it describes.
The announcement half handles timing. Associating the message helps a user who returns to the field, but it does nothing for the user who clicks Submit and expects to learn immediately that something failed. That is what a live region does: an element marked with aria-live (commonly aria-live="assertive" or role="alert" for errors, which carries an implicit assertive politeness) is watched by the screen reader, and when its contents change, the new text is announced without moving the user’s focus. So the moment validation injects the error message into that region, the user hears it, in time to act.
A concrete example: a signup form has an email field that fails server-side validation on submit. Done visually only, the page re-renders with a red border and the words “Email already in use” under the field; a sighted user sees it, a screen reader user hears nothing and sits waiting for a page that, as far as they can tell, did nothing. Done right, on submit focus moves to the first invalid field, that field carries aria-invalid="true" and aria-describedby linking to the message, and an error summary in an aria-live region announces “1 error: Email already in use,” so the failure is spoken at the moment it happens and reachable when the user navigates back.
Worth flagging: live regions are timing-sensitive and easy to get wrong. A live region must exist in the DOM before its content changes, because injecting both the region and its text at the same time often produces no announcement; create the empty container up front and write into it on error. Reserve assertive announcements for genuine errors that interrupt, and use the polite level for status confirmations so you are not constantly cutting off the user’s reading. And do not rely on moving focus alone to announce success or failure, since a focus move reads the focused element, not your message.
When you wire up validation, give each field an aria-invalid state and an aria-describedby link to its error text, place errors into a live region that already exists in the page, and on submit move focus to the first problem. Then run the form once with a screen reader and a keyboard: trigger a failure and confirm you hear the error at the moment it appears and again when you navigate to the field. If the error is only visible, it does not exist for the people who most need to hear it.