whiskeyjack
libannouncesource: ok · refreshed
announce
Announce a message to assistive technology through a shared live region.
npx shadcn add "https://whiskeyjack.net/r/announce.json"free
No selection tags yet — empty means no evidence, not lack of support.
Files1 · source cached 10/8/2026, 6:55:28 AM
lib/announce.ts
/**
* Announce a message to assistive technology via a shared, visually-hidden
* ARIA live region. Use for status that has no visible text anchor of its own
* -- route changes, async results, game-state transitions, timers.
*
* Two singleton regions are created lazily on `<body>` (one polite, one
* assertive) and reused. The text is cleared and re-set on the next frame so
* repeating the same message still re-announces.
*
* (Visible status that lives in a component should instead use that component's
* own live semantics -- e.g. a `Notice`/`Toast` with an aria-live role.)
*/
export type Politeness = "polite" | "assertive";
function getRegion(politeness: Politeness): HTMLElement {
const id = `wj-live-${politeness}`;
let region = document.getElementById(id);
if (!region) {
region = document.createElement("div");
region.id = id;
region.className = "wj-sr-only";
region.setAttribute("aria-live", politeness);
region.setAttribute("aria-atomic", "true");
region.setAttribute("role", politeness === "assertive" ? "alert" : "status");
document.body.appendChild(region);
}
return region;
}
export function announce(message: string, politeness: Politeness = "polite"): void {
if (typeof document === "undefined") return;
const region = getRegion(politeness);
region.textContent = "";
// Re-set on the next frame so an identical consecutive message re-announces.
requestAnimationFrame(() => {
region.textContent = message;
});
}