Accessibility · Forms and Errors

Inline Validation Timing and When Errors Should Speak

How validation on blur, on input, and on submit differ in what they interrupt, how debouncing and politeness control announcement, and why premature feedback degrades entry.

Validation has a timing dimension that markup cannot express. The same rule, evaluated at three different moments, produces three different experiences: an error shown while the user is still typing the first character of a phone number, an error shown when they leave the field, and an error shown when they press submit. The rule is identical in all three; only the moment differs, and the moment determines whether the message is help or interference.

For a keyboard and screen reader user the difference is sharper than for anyone else, because a message that appears while typing can interrupt speech mid-word. A polite live region announcement queued during a keystroke may be spoken before the character the user just entered is echoed, or after, depending on the queue’s state, and either way the user’s model of what they typed and what the field contains can drift.

The three moments and what each one costs

On submit is the default and the only moment the browser itself uses. Nothing is announced until the user has finished, so the form never interrupts. The cost is the burst: every failing field is discovered at once, and the user must work through them. Native validation focuses the first invalid control, which means the errors are presented one at a time and each fix requires another submit. This is why the summary pattern in Error Summaries and Focus Placement exists: it converts the serial discovery into a single report.

On blur validates when focus leaves the field. It is the point at which the user has declared the value finished, so a complaint is timely and the field is still close at hand. The failure mode is a user who tabs through a form to read it before filling it: every field they pass through is validated empty, and a required field reports “This field is required” before the user has attempted anything. Validating on blur only after the field has been touched and non-empty, or only after a first submission attempt, avoids that.

On input validates on every keystroke. It is the most responsive and the most hostile. A pattern requiring a complete value fails on the first character of every valid entry, so the user sees an error state on a field that is on its way to being correct. For a screen reader user, each failure may produce an announcement, and a message that fires on character one and clears on character twelve is eleven announcements of a problem that resolved itself.

The pattern that survives all three cases is to defer until the field has been blurred once, then switch to input-time validation for that field. The first evaluation happens at a moment the user signalled completion, and subsequent corrections are validated live because the user is now known to be editing an already-judged value.

Announcement without interruption

A message that appears visually does not announce itself. Making it audible requires either moving focus, which is disruptive mid-form, or a live region, which is the mechanism designed for exactly this: content changes that should be announced without moving focus.

Politeness is the central control. aria-live="polite" queues the announcement until the user is idle; aria-live="assertive" interrupts whatever is being spoken. Assertive is appropriate for a submission failure that changes the page’s meaning, and inappropriate for a field-level correction, because interrupting a user mid-word to tell them their email is missing an @ is worse than waiting two seconds.

Two properties of live regions determine whether they work at all. The region must exist in the accessibility tree before its content changes; a region inserted into the DOM at the same moment it is populated is frequently not registered as a live region by the time the change is observed, and nothing is announced. And the change must be a content change inside the region, not a replacement of the region, because replacing the node discards its live status.

<p id="field-status" role="status" aria-live="polite" aria-atomic="true"></p>

role="status" implies aria-live="polite" and is the conventional choice for a field-level message. aria-atomic="true" makes the reader announce the whole region rather than only the changed text node, which matters when a message is assembled from a prefix and a detail, because without atomicity the reader may announce only the detail and lose the field name.

The region is placed near the field but outside it, and its text is set rather than appended. Appending messages accumulates them, and a field corrected three times leaves three messages in the region, all announced on the next change.

Debouncing and the announcement queue

Input-time validation needs a delay, and the delay serves two purposes: it avoids evaluating a value that is still being typed, and it avoids queueing announcements faster than they can be spoken. A typical value is a few hundred milliseconds of idle time after the last keystroke, which is long enough that ordinary typing does not trigger evaluation between characters and short enough that a pause reads as completion.

The delay interacts with the live region’s own queue. A polite announcement is not spoken over the current utterance; it waits, and implementations differ in whether a second change replaces a pending message or follows it. Rapid changes can therefore produce either a backlog spoken after the user has moved on or a message that is overwritten before it is ever heard. Setting the region’s text to the current message rather than appending, and skipping the update entirely when the message is unchanged, keeps the region to one entry per actual state transition.

The comparison to make is against the previous message, not against the previous value. A field that goes from no error to “too short” to “still too short” has one transition to announce; the second evaluation produced the same string and should produce no announcement.

Announcing success

A message that clears is also a change, and whether it should be announced is a real decision. Clearing the region silently means the user learns the field is valid only by re-reading it or by the absence of an error, and absence is not perceivable. Writing “Looks good” into the region announces a resolution.

The argument against announcing success on every field is verbosity: a ten-field form becomes twenty announcements, half of them confirmations. The argument for is that a screen reader user has no visual cue that the error styling disappeared. A middle position is to announce success only after a field has had an error and now does not, which is exactly the transition the user needs confirmed, and to say nothing for a field that was never in error.

For a validation that happens on blur of a field the user has not yet submitted, an announcement of success is noise; for one that happens after a failed submission, it is the confirmation that the fix landed. The state that distinguishes them is whether the form has been submitted at least once.

Interaction with the field’s description

The message announced by a live region and the message attached to the field with aria-describedby are two deliveries of the same string. If both are active, the user hears the message when it appears and again when focus returns to the field. That duplication is usually correct: the first announcement is the notification, the second is the reminder on re-entry.

The failure case is a message written into the live region without being attached to the field. The user hears it once, mid-form, and then has no way to retrieve it: the field’s description does not contain it, and revisiting the field announces the field as if it were fine. Both wirings are needed, and they are described in Wiring Error Messages to Their Fields.

There is a subtler interaction with aria-invalid. Changing it from false to true on a focused control can itself trigger an announcement in some screen readers, independent of any live region, because the state change is reported. Combined with a live region message, that is two announcements for one event. Some implementations avoid setting aria-invalid on the focused control until blur and rely on the live region alone while the user is typing.

Rules that hold across implementations

Four invariants are worth stating because they survive differences between engines and screen readers.

No announcement fires before the user has finished entering a value. The definition of finished is blur, or a pause long enough to read as one.

No announcement fires for a message identical to the one already delivered. Compare strings, not values.

The live region exists before it is needed. It is rendered with the form and its text changes; it is not created on demand.

Assertive announcements are reserved for page-level failures. Field-level messages are polite, and the submission summary is the one place where interrupting is justified.

Everything else, from the exact delay to whether success is announced to whether validation switches to input-time after the first blur, is a trade between responsiveness and interruption, and the axis runs the same way in every case: the earlier the feedback, the more it interrupts; the later, the more work it leaves the user to do in one burst.

References

  1. WAI-ARIA 1.2: Live Regions — W3C
  2. Understanding Success Criterion 3.3.1: Error Identification — W3C WAI
  3. HTML Living Standard: Constraint validation — WHATWG