diff --git a/resources/boost/skills/livewire-material-development/SKILL.md b/resources/boost/skills/livewire-material-development/SKILL.md
index 38ecc56c..696f0a57 100644
--- a/resources/boost/skills/livewire-material-development/SKILL.md
+++ b/resources/boost/skills/livewire-material-development/SKILL.md
@@ -326,7 +326,7 @@ The snackbar host. Once per layout, near the end of `
`: `` (`po
materialToast('Share deleted', { type: 'success', description: null, timeout: 4000, action: { label: 'Undo', handler: () => $wire.restore() } })
```
-`type` (`success`, `error`, `warning`, `info`) adds the state icon; `timeout: 0` keeps it until dismissed; a toast with an action or no timeout gets a close button. Hover or focus pauses the timer.
+`type` (`success`, `error`, `warning`, `info`) picks the announcement role; `timeout: 0` keeps it until dismissed; a toast with an action or no timeout gets a close button. Hover or focus pauses the timer. A toast with an `action` never auto-dismisses (M3's rule) unless you write a `timeout` out.
- `action`: `label`, plus `handler` (a function) and/or `event` (a name). Pressing it closes the snackbar, calls `handler`, then dispatches `new CustomEvent(event)` on `window`; give both and both run. Use `event` where a function cannot travel, such as a toast built from JSON.
- `sticky: true` keeps a toast until it is dismissed or its action is pressed (any `timeout` is ignored), without holding the queue up: a toast dispatched meanwhile shows in its place, and the sticky one comes back once the queue is empty. One sticky toast is kept at a time; a newer one replaces it. Use it for a question that must be answered, not for news:
diff --git a/resources/js/snackbar.js b/resources/js/snackbar.js
index 8d3cf59f..87ee4515 100644
--- a/resources/js/snackbar.js
+++ b/resources/js/snackbar.js
@@ -3,7 +3,8 @@
*
* One snackbar at a time, as M3 shows them. Each waits its turn, stays for its timeout (paused
* while hovered or focused, so it is never pulled away from someone reading or reaching for its
- * action) and is replaced by the next.
+ * action) and is replaced by the next. A snackbar carrying an action has no timeout at all —
+ * M3's accessibility page forbids one — unless the caller writes a timeout out.
*
* A `sticky` toast ("A new version is ready" with a Reload action) stays until it is answered, but
* never holds the queue up: it is kept aside rather than queued, a toast that arrives while it shows
@@ -52,12 +53,17 @@ document.addEventListener('alpine:init', () => {
const toast = Array.isArray(detail) ? detail[0] : detail
const sticky = toast.sticky === true
+ // M3 forbids a snackbar with an action from auto-dismissing: it has to wait for the
+ // person to read it and reach it. A timeout written out still wins, for a caller who
+ // means it; a missing one no longer falls back to the default.
+ const untimed = sticky || toast.timeout === 0 || toast.timeout === null || (toast.action != null && toast.timeout === undefined)
+
const entry = {
id: ++sequence,
type: toast.type ?? null,
title: toast.title ?? '',
description: toast.description ?? null,
- timeout: sticky || toast.timeout === 0 || toast.timeout === null ? 0 : (toast.timeout ?? DEFAULT_TIMEOUT_MS),
+ timeout: untimed ? 0 : (toast.timeout ?? DEFAULT_TIMEOUT_MS),
action: toast.action ?? null,
sticky,
}
diff --git a/resources/views/components/toast.blade.php b/resources/views/components/toast.blade.php
index fb09d3c7..1227beeb 100644
--- a/resources/views/components/toast.blade.php
+++ b/resources/views/components/toast.blade.php
@@ -7,9 +7,10 @@
dispatches from a Livewire component — and for `window.materialToast(title, options)` from
JavaScript (`{ type, description, timeout, sticky, action: { label, handler, event } }`).
Toasts queue and show in turn, each for its `timeout` (4s by default; M3 asks for 4–10s),
- paused while the pointer or focus is on it. A toast with an action or no timeout gets a close
- button. Pressing the action closes the snackbar, calls `handler` and dispatches `event` (a
- name) on `window`; both may be given.
+ paused while the pointer or focus is on it. A toast with an `action` has no timeout at all,
+ as M3 requires — it waits to be read and acted on — unless the caller writes a `timeout` out.
+ A toast with an action or no timeout gets a close button. Pressing the action closes the
+ snackbar, calls `handler` and dispatches `event` (a name) on `window`; both may be given.
`sticky: true` keeps a toast until it is dismissed or its action pressed, without holding up
the queue: a toast that arrives meanwhile shows in its place, and the sticky one comes back