fix(a11y): Toggle aria-invalid briefly when async onChange rejects
Some checks failed
check / cargo check (macos-latest) (push) Has been cancelled
check / cargo check (ubuntu-22.04) (push) Has been cancelled
check / cargo check (windows-latest) (push) Has been cancelled
check / svelte build + lint (push) Has been cancelled

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:
2026-05-07 11:31:47 +01:00
parent aecb191705
commit 2dfd7167fd

View File

@@ -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