fix(a11y): Toggle aria-invalid briefly when async onChange rejects
When an async onChange handler rejects, the toggle snapped back to its previous value silently — screen reader users had no signal that the action failed, only that the toggle suddenly returned to its original state. Now aria-invalid="true" exposes the failure on the role=switch button for 1.5s after rejection, so assistive tech announces the error. Self-clears via setTimeout; no API change for callers.
This commit is contained in:
@@ -17,6 +17,10 @@
|
||||
// Kept separate from the `loading` prop so callers can also force loading externally.
|
||||
let pending = $state(false);
|
||||
|
||||
// Briefly true after an async onChange rejects, so screen readers hear that
|
||||
// the action failed rather than silently inferring it from the snap-back.
|
||||
let snapBack = $state(false);
|
||||
|
||||
// Effective busy state covers both the externally-controlled prop and our internal pending flag.
|
||||
let busy = $derived(loading || pending);
|
||||
|
||||
@@ -41,7 +45,11 @@
|
||||
await result;
|
||||
checked = next;
|
||||
} catch (_) {
|
||||
// Swallow; the toggle simply stays at its previous value.
|
||||
// Swallow; the toggle stays at its previous value. Flag the snap-back
|
||||
// briefly so assistive tech announces aria-invalid="true" and screen
|
||||
// reader users hear that the action failed.
|
||||
snapBack = true;
|
||||
setTimeout(() => { snapBack = false; }, 1500);
|
||||
} finally {
|
||||
pending = false;
|
||||
}
|
||||
@@ -68,6 +76,7 @@
|
||||
aria-label={label}
|
||||
aria-disabled={disabled ? "true" : undefined}
|
||||
aria-busy={busy ? "true" : undefined}
|
||||
aria-invalid={snapBack ? "true" : undefined}
|
||||
>
|
||||
<span
|
||||
class="absolute top-[3px] left-[3px] w-4 h-4 rounded-full shadow-sm flex items-center justify-center
|
||||
|
||||
Reference in New Issue
Block a user