a11y: app toast notifications become a live region for screen readers
showAppToast spawns / reuses a single floating toast at the bottom- right of the window for transient status (e.g. "1 new VOD auto-queued", "Cannot start recording", etc). The toast had no a11y semantics — screen readers never announced it, so the entire transient-feedback channel was silent for AT users. Promoted the toast container to a live region: - role="status" for info toasts + aria-live="polite" so the reader waits for a natural break in current speech before announcing - role="alert" for warn toasts + aria-live="assertive" so the reader interrupts whatever it was saying (matches the visual amber-left- border meaning — warn IS urgent) - aria-atomic="true" so the reader announces the whole message at once instead of attempting to diff against the previous toast Critical detail: aria-live attributes have to be in place BEFORE the text changes for AT to register the change as a live-region update. The current implementation now sets role / aria-live first and only then writes the new textContent. WCAG 4.1.3 — Status Messages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7be9453762
commit
5d5e58ae09
@ -689,14 +689,28 @@ function showAppToast(message: string, type: 'info' | 'warn' = 'info'): void {
|
||||
toast = document.createElement('div');
|
||||
toast.id = 'appToast';
|
||||
toast.className = 'app-toast';
|
||||
// Live region — screen readers announce the toast text whenever
|
||||
// it changes. Warn toasts go through aria-live="assertive" so the
|
||||
// reader interrupts whatever it was speaking; info toasts use
|
||||
// "polite" so they wait for a natural break in current speech.
|
||||
toast.setAttribute('role', 'status');
|
||||
toast.setAttribute('aria-live', 'polite');
|
||||
toast.setAttribute('aria-atomic', 'true');
|
||||
document.body.appendChild(toast);
|
||||
}
|
||||
|
||||
toast.textContent = message;
|
||||
toast.classList.remove('warn', 'show');
|
||||
if (type === 'warn') {
|
||||
toast.classList.add('warn');
|
||||
toast.setAttribute('role', 'alert');
|
||||
toast.setAttribute('aria-live', 'assertive');
|
||||
} else {
|
||||
toast.setAttribute('role', 'status');
|
||||
toast.setAttribute('aria-live', 'polite');
|
||||
}
|
||||
// Setting textContent AFTER the aria-live attribute is in place
|
||||
// ensures the change is captured as a live-region update by AT.
|
||||
toast.textContent = message;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
toast?.classList.add('show');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user