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 = document.createElement('div');
|
||||||
toast.id = 'appToast';
|
toast.id = 'appToast';
|
||||||
toast.className = 'app-toast';
|
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);
|
document.body.appendChild(toast);
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.textContent = message;
|
|
||||||
toast.classList.remove('warn', 'show');
|
toast.classList.remove('warn', 'show');
|
||||||
if (type === 'warn') {
|
if (type === 'warn') {
|
||||||
toast.classList.add('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(() => {
|
requestAnimationFrame(() => {
|
||||||
toast?.classList.add('show');
|
toast?.classList.add('show');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user