Accessibility: Screen Reader Error Announcements on Forms (A11Y-20)
Accessibility: Screen Reader Error Announcements on Forms
Release: v1.0.446 · Audit item: A11Y-20 · WCAG criteria: 3.3.1, 1.3.1
Overview
As of v1.0.446, all five forms in the application correctly announce validation errors to screen reader users. Previously, when a form submission failed or real-time validation fired, errored inputs carried no programmatic indication of their invalid state, meaning users of assistive technologies (NVDA, JAWS, VoiceOver) heard nothing when tabbing back to a field that needed correction.
This release resolves audit item A11Y-20 and brings those forms into full compliance with WCAG 2.1 success criteria:
- 3.3.1 Error Identification (Level A) — errors are identified and described in text
- 1.3.1 Info and Relationships (Level A) — structure conveyed visually is also available to assistive technologies
Affected Forms
| Form | Location |
|---|---|
| Sign-in | /sign-in |
| Sign-up | /sign-up |
| Profile settings | /dashboard/settings (profile section) |
| Organisation settings | /dashboard/settings (org section) |
| Feedback widget | Global (available on all dashboard pages) |
How It Works
aria-invalid
Every input that can enter an error state now sets aria-invalid={true} when an error is present and aria-invalid={false} when it is not. This attribute is the standard signal assistive technologies use to determine whether a field needs attention.
<input
aria-invalid={!!submitError}
aria-describedby={submitError ? "profile-form-error" : undefined}
...
/>
aria-describedby
When an error is active, aria-describedby is set to the id of the error message container. Each form uses a unique ID so there is no ambiguity across multiple forms on the same page:
| Form | Error container ID |
|---|---|
| Sign-in | signin-form-error |
| Sign-up | signup-form-error |
| Profile settings | profile-form-error |
| Organisation settings | org-form-error |
| Feedback widget | feedback-error |
When there is no error, aria-describedby is set to undefined (omitted from the DOM) so the browser does not maintain a stale reference.
Password field on sign-up
The password input always references signup-password-hint via aria-describedby, ensuring the requirements paragraph is announced regardless of error state. The hint text changes dynamically:
| State | Announced text |
|---|---|
| No password entered | "Must be at least 10 characters. We'll check your password against known data breaches to keep your account secure." |
| Password too weak | "Please choose a stronger password (at least 10 characters with a mix of letters, numbers, or symbols)." |
| Password acceptable | (empty — no redundant announcement) |
When a submit error also exists, aria-invalid is set to true and aria-describedby switches to signup-form-error so the server-side error message is read instead.
Feedback widget textarea
The feedback textarea previously had no associated <label>, making it unlabelled for screen reader users. A visually-hidden label (Feedback message) has been added using the sr-only utility class:
<label htmlFor="feedback-message" className="sr-only">
Feedback message
</label>
<textarea
id="feedback-message"
aria-invalid={!!error}
aria-describedby={error ? "feedback-error" : undefined}
...
/>
The error container in this widget has also been upgraded to include role="alert" so that errors are announced immediately when they appear, consistent with all other forms.
No Breaking Changes
All changes are purely additive HTML attribute changes. No visual appearance, behaviour, API, or data model has been altered.