Skip to content

Improve screen reader announcements for auth feedback#1603

Open
web3blind wants to merge 2 commits into
Ride-The-Lightning:masterfrom
web3blind:a11y-live-region-announcements
Open

Improve screen reader announcements for auth feedback#1603
web3blind wants to merge 2 commits into
Ride-The-Lightning:masterfrom
web3blind:a11y-live-region-announcements

Conversation

@web3blind
Copy link
Copy Markdown

Summary

  • Add live-region semantics to the initial RTL loading state.
  • Mark authentication form validation errors as assertive alerts.
  • Announce login errors assertively and logout/session messages politely.

Accessibility impact

These changes help screen reader users hear important dynamic status and validation feedback when it appears, instead of needing to manually search the page after a failed login/authentication action.

Fixes #1561.

Checks

  • npm run lint -- --quiet
  • npx ng build --configuration development --optimization false

Note: npm ci required --legacy-peer-deps because the current dependency tree has an Angular/zone.js peer dependency conflict.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces accessibility enhancements across several components by adding ARIA roles and live region attributes to loading indicators, error messages, and status updates. The review feedback identifies several opportunities to improve these changes by removing redundant attributes where roles already imply specific live region behaviors (e.g., role="status" and role="alert"). Additionally, it is recommended to use aria-live="polite" instead of "assertive" for form validation errors to avoid disruptive screen reader announcements during user interaction.

Comment thread src/app/app.component.html Outdated
Comment on lines +32 to +33
<div *ngIf="!selNode.settings.themeColor" class="rtl-spinner" role="status" aria-live="polite" aria-atomic="true">
<mat-spinner color="accent" aria-label="Loading RTL" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The role="status" attribute implicitly includes aria-live="polite" and aria-atomic="true", making the explicit attributes redundant. Additionally, the aria-label on mat-spinner repeats the text in the h4 element, which will cause screen readers to announce the message twice. It is better to hide the decorative spinner from assistive technology.

Suggested change
<div *ngIf="!selNode.settings.themeColor" class="rtl-spinner" role="status" aria-live="polite" aria-atomic="true">
<mat-spinner color="accent" aria-label="Loading RTL" />
<div *ngIf="!selNode.settings.themeColor" class="rtl-spinner" role="status">
<mat-spinner color="accent" aria-hidden="true" />

<mat-label>Password</mat-label>
<input autoFocus matInput type="password" id="password" name="password" tabindex="1" required [(ngModel)]="password">
<mat-error *ngIf="!password">Password is required.</mat-error>
<mat-error *ngIf="!password" role="alert" aria-live="assertive">Password is required.</mat-error>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using aria-live="assertive" for form validation errors is generally discouraged as it can interrupt the user's current interaction. mat-error is already associated with the input via aria-describedby in Angular Material. If a live announcement is needed when the error appears, aria-live="polite" is the standard approach to ensure the user is notified without being abruptly interrupted.

Suggested change
<mat-error *ngIf="!password" role="alert" aria-live="assertive">Password is required.</mat-error>
<mat-error *ngIf="!password" aria-live="polite">Password is required.</mat-error>

<mat-icon>{{flgShow ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
<mat-error *ngIf="!password">Password is required.</mat-error>
<mat-error *ngIf="!password" role="alert" aria-live="assertive">Password is required.</mat-error>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Inline validation errors should typically use aria-live="polite". Assertive announcements are reserved for critical, time-sensitive information and can be disruptive when triggered during form entry. Since Angular Material already links mat-error to the input via aria-describedby, a polite announcement is sufficient for accessibility.

Suggested change
<mat-error *ngIf="!password" role="alert" aria-live="assertive">Password is required.</mat-error>
<mat-error *ngIf="!password" aria-live="polite">Password is required.</mat-error>

<mat-error *ngIf="!password" role="alert" aria-live="assertive">Password is required.</mat-error>
</mat-form-field>
<p *ngIf="loginErrorMessage !== ''" class="color-warn pre-wrap" fxFlex="100" fxLayoutAlign="start center">
<p *ngIf="loginErrorMessage !== ''" class="color-warn pre-wrap" fxFlex="100" fxLayoutAlign="start center" role="alert" aria-live="assertive" aria-atomic="true">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The role="alert" attribute already implies aria-live="assertive" and aria-atomic="true". Removing these redundant attributes simplifies the template code.

Suggested change
<p *ngIf="loginErrorMessage !== ''" class="color-warn pre-wrap" fxFlex="100" fxLayoutAlign="start center" role="alert" aria-live="assertive" aria-atomic="true">
<p *ngIf="loginErrorMessage !== ''" class="color-warn pre-wrap" fxFlex="100" fxLayoutAlign="start center" role="alert">

{{loginErrorMessage}}
</p>
<p *ngIf="logoutReason !== ''" class="color-warn pre-wrap" fxLayout="row" fxFlex="100" fxLayoutAlign="start center">
<p *ngIf="logoutReason !== ''" class="color-warn pre-wrap" fxLayout="row" fxFlex="100" fxLayoutAlign="start center" role="status" aria-live="polite" aria-atomic="true">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The role="status" attribute already implies aria-live="polite" and aria-atomic="true". These explicit attributes can be removed to reduce redundancy.

Suggested change
<p *ngIf="logoutReason !== ''" class="color-warn pre-wrap" fxLayout="row" fxFlex="100" fxLayoutAlign="start center" role="status" aria-live="polite" aria-atomic="true">
<p *ngIf="logoutReason !== ''" class="color-warn pre-wrap" fxLayout="row" fxFlex="100" fxLayoutAlign="start center" role="status">

<mat-label>Current Password</mat-label>
<input autoFocus matInput type="password" id="currpassword" name="currpassword" tabindex="1" required [(ngModel)]="currPassword">
<mat-error *ngIf="!currPassword">Current password is required.</mat-error>
<mat-error *ngIf="!currPassword" role="alert" aria-live="assertive">Current password is required.</mat-error>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For form validation, aria-live="polite" is preferred over assertive. Assertive regions interrupt the screen reader's current speech, which can be confusing when the user is moving between fields or typing. mat-error is already handled by Angular Material's accessibility features.

Suggested change
<mat-error *ngIf="!currPassword" role="alert" aria-live="assertive">Current password is required.</mat-error>
<mat-error *ngIf="!currPassword" aria-live="polite">Current password is required.</mat-error>

<mat-label>New Password</mat-label>
<input matInput type="password" id="newpassword" name="newpassword" tabindex="2" required [(ngModel)]="newPassword">
<mat-error *ngIf="matchOldAndNewPasswords()">{{errorMsg}}</mat-error>
<mat-error *ngIf="matchOldAndNewPasswords()" role="alert" aria-live="assertive">{{errorMsg}}</mat-error>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Validation messages that update as the user types (like password matching) should not be assertive, as they will repeatedly interrupt the user. Using polite allows the screen reader to announce the error at the next available opportunity.

Suggested change
<mat-error *ngIf="matchOldAndNewPasswords()" role="alert" aria-live="assertive">{{errorMsg}}</mat-error>
<mat-error *ngIf="matchOldAndNewPasswords()" aria-live="polite">{{errorMsg}}</mat-error>

<mat-label>Confirm New Password</mat-label>
<input matInput type="password" id="confirmpassword" name="confirmpassword" tabindex="3" required [(ngModel)]="confirmPassword">
<mat-error *ngIf="matchNewPasswords()">{{errorConfirmMsg}}</mat-error>
<mat-error *ngIf="matchNewPasswords()" role="alert" aria-live="assertive">{{errorConfirmMsg}}</mat-error>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider changing this to aria-live="polite" to follow accessibility best practices for non-critical form validation feedback.

Suggested change
<mat-error *ngIf="matchNewPasswords()" role="alert" aria-live="assertive">{{errorConfirmMsg}}</mat-error>
<mat-error *ngIf="matchNewPasswords()" aria-live="polite">{{errorConfirmMsg}}</mat-error>

@saubyk saubyk added this to the 0.15.9 milestone May 21, 2026
@saubyk
Copy link
Copy Markdown
Collaborator

saubyk commented May 21, 2026

/rtl review

@rtlreview
Copy link
Copy Markdown

rtlreview Bot commented May 21, 2026

✅ Review posted: #1603 (review)

4 finding(s); 4 inline, 0 in body.

🔁 Need a re-review after pushing changes? Reply with /rtl re-review.
Maintainers can also /rtl dismiss <id> to silence specific findings, or anyone can /rtl explain <id> for elaboration.

Copy link
Copy Markdown

@rtlreview rtlreview Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds ARIA live-region semantics to authentication-related feedback (loading spinner, form validation errors, login/logout messages) to improve screen reader announcements. The intent is sound and the changes touch the right surfaces. The main issue is that the loading spinner's aria-label duplicates the visible heading inside the same aria-atomic live region, which will cause screen readers to announce "Loading RTL" twice. A secondary concern is the consistent use of aria-live="assertive" for inline form validation, which interrupts ongoing speech and is generally discouraged for non-critical, field-level errors. The redundant explicit attributes on role="status" / role="alert" elements are harmless but worth a quick pass.

Comment thread src/app/app.component.html Outdated
<div *ngIf="!selNode.settings.themeColor" class="rtl-spinner">
<mat-spinner color="accent" />
<div *ngIf="!selNode.settings.themeColor" class="rtl-spinner" role="status" aria-live="polite" aria-atomic="true">
<mat-spinner color="accent" aria-label="Loading RTL" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 F1 (major): The loading region uses aria-atomic="true" and contains both <mat-spinner aria-label="Loading RTL" /> and <h4>Loading RTL...</h4>. Because aria-atomic="true" causes the entire region's accessible name/text to be announced as a unit, screen readers will read "Loading RTL" from the spinner's aria-label and then "Loading RTL..." from the heading — the same message twice. The spinner is decorative here; mark it aria-hidden="true" (and drop aria-label) so only the <h4> text is announced. Compare: <mat-spinner color="accent" aria-hidden="true" />.

<mat-label>Password</mat-label>
<input autoFocus matInput type="password" id="password" name="password" tabindex="1" required [(ngModel)]="password">
<mat-error *ngIf="!password">Password is required.</mat-error>
<mat-error *ngIf="!password" role="alert" aria-live="assertive">Password is required.</mat-error>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 F2 (minor): aria-live="assertive" on inline form validation errors interrupts any in-progress screen reader speech, which is jarring while a user is typing or moving between fields. WAI-ARIA authoring guidance reserves assertive for time-critical updates (e.g. session-about-to-expire); routine "field is required" messages should use polite. Angular Material already associates mat-error with the input via aria-describedby, so the message is reachable when focus returns to the field — polite is sufficient. The same concern applies to the three mat-error elements in auth-settings.component.html (lines 10, 15, 20) and the login form's mat-error (login.component.html:22).

<mat-error *ngIf="!password" role="alert" aria-live="assertive">Password is required.</mat-error>
</mat-form-field>
<p *ngIf="loginErrorMessage !== ''" class="color-warn pre-wrap" fxFlex="100" fxLayoutAlign="start center">
<p *ngIf="loginErrorMessage !== ''" class="color-warn pre-wrap" fxFlex="100" fxLayoutAlign="start center" role="alert" aria-live="assertive" aria-atomic="true">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 F3 (minor): The login error path is a reasonable place to use assertive announcement (failed login is a high-signal event the user is waiting for), so the role="alert" here is fine. However, role="alert" implicitly carries aria-live="assertive" and aria-atomic="true"; the explicit duplicate attributes are redundant. Same redundancy applies to role="status" on line 28 (which implies aria-live="polite" + aria-atomic="true") and to every mat-error that now carries both role="alert" and aria-live="assertive". Not a defect — some teams prefer explicit-over-implicit — but worth choosing one style consistently across these files.

<mat-icon>{{flgShow ? 'visibility_off' : 'visibility'}}</mat-icon>
</button>
<mat-error *ngIf="!password">Password is required.</mat-error>
<mat-error *ngIf="!password" role="alert" aria-live="assertive">Password is required.</mat-error>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 F4 (nit): On mat-error, the added role="alert" will cause the message to be announced whenever the element is inserted into the DOM. In Angular Material the error appears as soon as the control becomes invalid (typically after first interaction), which on a fresh autofocused empty password field can cause "Password is required" to be announced before the user has done anything meaningful. Consider whether the aria-describedby linkage Material already sets up is sufficient on its own, and reserve the live-region announcement for the post-submit loginErrorMessage path (which this PR already covers separately).

@rtlreview rtlreview Bot added the rtl-active label May 21, 2026
@rtlreview
Copy link
Copy Markdown

rtlreview Bot commented May 21, 2026

🤖 rtlreviewbot audit metadata for this PR — auto-generated, please don't edit.

@web3blind
Copy link
Copy Markdown
Author

/rtl re-review

Updated the PR to address the requested accessibility feedback:

  • made the decorative loading spinner hidden from assistive tech so the loading text is not announced twice;
  • removed redundant explicit live-region attributes where the role already provides the semantics;
  • avoided assertive inline validation announcements for routine auth form feedback.

Local validation: npm run lint -- --quiet passed.

@rtlreview
Copy link
Copy Markdown

rtlreview Bot commented May 31, 2026

Sorry, /rtl re-review requires maintainer permission.

@saubyk
Copy link
Copy Markdown
Collaborator

saubyk commented May 31, 2026

/rtl re-review

@rtlreview
Copy link
Copy Markdown

rtlreview Bot commented May 31, 2026

✅ Re-review posted: #1603 (review)

Prior findings: 4 addressed, 0 still unresolved, 0 withdrawn.
New findings: 0.
Total inline: 0; in body: 0.

🔁 Need another re-review after pushing changes? Reply with /rtl re-review.
Maintainers can also /rtl dismiss <id> to silence specific findings, or anyone can /rtl explain <id> for elaboration.

Copy link
Copy Markdown

@rtlreview rtlreview Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new commit fully addresses all four prior findings. The decorative spinner is now hidden from assistive technology, the redundant explicit live-region attributes have been removed where the role already conveys the semantics, and every mat-error now uses aria-live="polite" rather than assertive. The role="alert" on loginErrorMessage and role="status" on logoutReason are appropriately retained as the events they describe warrant their respective announcement urgencies.

No new concerns are introduced by the updated diff.


Status of prior findings

  • F1 addressed: Resolved in src/app/app.component.html:32-33. The spinner now carries aria-hidden="true" (and no aria-label), so the wrapper's role="status" only exposes the <h4>Loading RTL...</h4> text — single announcement, no duplication.
  • F2 addressed: Every mat-error flagged in the prior review now uses aria-live="polite" rather than assertive: is-authorized.component.html:14, login.component.html:22, and auth-settings.component.html:10,15,20. Validation feedback will queue instead of interrupting in-progress speech.
  • F3 addressed: The explicit aria-live / aria-atomic duplicates on role="alert" and role="status" elements were dropped in this revision. login.component.html:23 now carries only role="alert" and login.component.html:27 only role="status"; the mat-error elements no longer double-up role + explicit live attributes.
  • F4 addressed: The mat-error at login.component.html:22 no longer carries role="alert"; it uses aria-live="polite". The autofocus + insert-on-invalid path will no longer interrupt the user, and the polite live region still complements the aria-describedby linkage Angular Material wires up automatically.

@saubyk
Copy link
Copy Markdown
Collaborator

saubyk commented May 31, 2026

/rtl approve

Copy link
Copy Markdown

@rtlreview rtlreview Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Approved — all prior findings addressed.

Findings recap

Findings:

  • F1 (major) — addressed: The loading region uses aria-atomic="true" and contains both <mat-spinner aria-label="Loading RTL" /> and <h4>Loading RTL...</h4>. Bec…
  • F2 (minor) — addressed: aria-live="assertive" on inline form validation errors interrupts any in-progress screen reader speech, which is jarring while a user is t…
  • F3 (minor) — addressed: The login error path is a reasonable place to use assertive announcement (failed login is a high-signal event the user is waiting for), so t…
  • F4 (nit) — addressed: On mat-error, the added role="alert" will cause the message to be announced whenever the element is inserted into the DOM. In Angular Ma…

Approved by @saubyk via /rtl approve. Last reviewed at b024ed5. Skill v0.1.0, model claude-opus-4-7.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[A11Y] [Medium] Missing ARIA live regions for dynamic content announcements

2 participants