Keep the snackbar's live region in the page
The region carrying aria-live was created together with its message, inside the x-if that draws the snackbar, so a screen reader had nothing to notice changing. It moves to the permanent host, which is polite and atomic as M3 asks; a type now only picks the region's role, and the explicit aria-live keeps an error polite too. Plan step 11, actions.md ACT-02. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
91f49be6d7
commit
04b655fa9b
@@ -15,6 +15,12 @@
|
|||||||
the queue: a toast that arrives meanwhile shows in its place, and the sticky one comes back
|
the queue: a toast that arrives meanwhile shows in its place, and the sticky one comes back
|
||||||
once the queue is empty. One is kept at a time; a newer sticky toast replaces it.
|
once the queue is empty. One is kept at a time; a newer sticky toast replaces it.
|
||||||
|
|
||||||
|
The live region is the host itself, not the snackbar: a region must be in the page before its
|
||||||
|
contents change for a screen reader to announce them reliably, and the snackbar comes and
|
||||||
|
goes. It is `aria-live="polite" aria-atomic="true"`, as M3 asks for a snackbar (never
|
||||||
|
assertive); a `type` picks the region's role — `alert` for an error or a warning, `status`
|
||||||
|
otherwise — and the explicit `aria-live` keeps even those polite.
|
||||||
|
|
||||||
Hooks for tests and styling: `data-toast` on the snackbar on screen, `data-toast-action` on its
|
Hooks for tests and styling: `data-toast` on the snackbar on screen, `data-toast-action` on its
|
||||||
action button.
|
action button.
|
||||||
|
|
||||||
@@ -33,6 +39,10 @@
|
|||||||
@persist('material-toast')
|
@persist('material-toast')
|
||||||
<div
|
<div
|
||||||
x-data="materialSnackbar"
|
x-data="materialSnackbar"
|
||||||
|
x-bind:role="current && (current.type === 'error' || current.type === 'warning') ? 'alert' : 'status'"
|
||||||
|
role="status"
|
||||||
|
aria-live="polite"
|
||||||
|
aria-atomic="true"
|
||||||
{{ $attributes->class([
|
{{ $attributes->class([
|
||||||
'pointer-events-none fixed inset-x-4 z-50 flex bottom-[calc(var(--material-bottom-bar,0px)+1rem)]',
|
'pointer-events-none fixed inset-x-4 z-50 flex bottom-[calc(var(--material-bottom-bar,0px)+1rem)]',
|
||||||
'justify-center' => $position !== 'bottom-start',
|
'justify-center' => $position !== 'bottom-start',
|
||||||
@@ -43,8 +53,6 @@
|
|||||||
<div
|
<div
|
||||||
x-bind:key="current.id"
|
x-bind:key="current.id"
|
||||||
data-toast
|
data-toast
|
||||||
x-bind:role="current.type === 'error' || current.type === 'warning' ? 'alert' : 'status'"
|
|
||||||
aria-live="polite"
|
|
||||||
x-on:mouseenter="pause()"
|
x-on:mouseenter="pause()"
|
||||||
x-on:mouseleave="resume()"
|
x-on:mouseleave="resume()"
|
||||||
x-on:focusin="pause()"
|
x-on:focusin="pause()"
|
||||||
|
|||||||
@@ -18,6 +18,16 @@ it('marks the snackbar and its action for tests and styling', function () {
|
|||||||
$html = (string) $this->blade('<x-toast />');
|
$html = (string) $this->blade('<x-toast />');
|
||||||
|
|
||||||
expect($html)
|
expect($html)
|
||||||
->toMatch('/<div\s[^>]*\bdata-toast\b[^>]*aria-live="polite"/')
|
->toMatch('/<div\s[^>]*\bdata-toast\b/')
|
||||||
->toMatch('/<button\s[^>]*\bdata-toast-action\b[^>]*x-on:click="act\(\)"/');
|
->toMatch('/<button\s[^>]*\bdata-toast-action\b[^>]*x-on:click="act\(\)"/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps the live region in the page, polite, around the snackbar that comes and goes', function () {
|
||||||
|
$html = (string) $this->blade('<x-toast />');
|
||||||
|
|
||||||
|
expect($html)
|
||||||
|
->toMatch('/<div\s[^>]*x-data="materialSnackbar"[^>]*aria-live="polite"/')
|
||||||
|
->toContain('aria-atomic="true"')
|
||||||
|
->toContain("x-bind:role=\"current && (current.type === 'error' || current.type === 'warning') ? 'alert' : 'status'\"")
|
||||||
|
->and(substr_count($html, 'aria-live'))->toBe(1);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user