From grimoire
Implements ARIA live regions to announce dynamic content changes (toasts, search results, form status, chat messages) to screen readers without moving focus.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-aria-live-regionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Announce dynamic content changes to screen readers using `aria-live` regions or semantic live region roles — without moving focus.
Announce dynamic content changes to screen readers using aria-live regions or semantic live region roles — without moving focus.
Adopted by: WCAG 2.1 SC 4.1.3 (Level AA) is required by Section 508 (US), EU EN 301 549, and UK PSBAR 2018. WAI-ARIA 1.2 live region support is implemented in all major screen readers (NVDA, JAWS, VoiceOver, TalkBack) since 2018. All major front-end frameworks (React, Angular, Vue) have accessibility guidance on live regions for dynamic content. Impact: Deque Systems research found that missing live region announcements are the #2 source of screen reader usability failures in single-page applications, after focus management. A screen reader user who submits a form and hears nothing cannot know if the submission succeeded, failed, or is loading — without a live region announcement, they must explore the page manually to find out. Why best: The alternative — moving focus to the new content — is disruptive for minor status updates (toast notifications, inline search suggestions). Live regions allow content to be announced without stealing focus, preserving the user's context and interaction flow.
Sources: W3C WCAG 2.1 SC 4.1.3 (2018); WAI-ARIA 1.2 Live Region specification; Deque University; MDN ARIA live regions
| Role / attribute | Urgency | Interrupts? | Use for |
|---|---|---|---|
role="status" / aria-live="polite" | Low | No — waits for user to finish | Success messages, loading complete, search result count |
role="alert" / aria-live="assertive" | High | Yes — announces immediately | Errors, warnings, time-sensitive messages |
role="log" | Low | No | Chat history, activity feeds |
role="timer" | Low | No | Countdown timers |
aria-live="off" | None | No | Disable announcements on a region |
Use polite by default. Only use assertive for critical errors or urgent warnings —
interrupting a screen reader user mid-sentence is disruptive.
<!-- Wrong — role="alert" added dynamically; browser may not register it -->
<script>
const el = document.createElement('div');
el.setAttribute('role', 'alert');
el.textContent = 'Error: invalid email';
document.body.appendChild(el);
</script>
<!-- Right — live region exists in DOM at page load; content is injected later -->
<div role="status" id="toast-region" aria-live="polite" aria-atomic="true"></div>
<script>
document.getElementById('toast-region').textContent = 'Profile saved successfully';
</script>
The live region container must be present in the DOM before the announcement. Dynamically creating the element and adding content simultaneously is unreliable.
aria-atomic to control how changes are announced<!-- aria-atomic="true" — entire region announced when any part changes -->
<div role="status" aria-live="polite" aria-atomic="true">
Showing 24 of 847 results
</div>
<!-- aria-atomic="false" (default) — only changed parts announced -->
<div role="log" aria-live="polite" aria-atomic="false">
<!-- Individual messages appended; each appended message announced -->
<p>Alice joined the chat</p>
<p>Bob sent a message</p>
</div>
For status messages with a single changing value (result count, progress), use
aria-atomic="true". For append-only logs (chat, notifications), use the default
aria-atomic="false".
Toast notification:
<div id="toast" role="status" aria-live="polite" aria-atomic="true"
class="toast" hidden></div>
<script>
function showToast(message) {
const toast = document.getElementById('toast');
toast.textContent = ''; // clear first to re-trigger announcement
toast.removeAttribute('hidden');
requestAnimationFrame(() => { // allow DOM update before setting content
toast.textContent = message;
});
setTimeout(() => toast.setAttribute('hidden', ''), 4000);
}
showToast('Your order has been placed.');
</script>
Loading state:
<div role="status" aria-live="polite" id="loading-status"></div>
<script>
function setLoading(isLoading) {
const status = document.getElementById('loading-status');
status.textContent = isLoading ? 'Loading results…' : 'Results loaded';
}
</script>
Search result count:
<p role="status" aria-live="polite" aria-atomic="true" id="result-count"></p>
<script>
function updateResultCount(count) {
document.getElementById('result-count').textContent =
count === 0 ? 'No results found' : `${count} results found`;
}
</script>
assertive announcements interrupt current speechpolite announcements wait for a speech pausearia-live creates announcement noise. Only announce changes users need to know about without actively exploring the page.Adding role="alert" to an element and content simultaneously. Some screen readers don't catch this. Pre-render the live region; inject content after.
Using aria-live="assertive" for non-urgent messages. Assertive interrupts whatever the screen reader is saying. Use polite for success messages, counts, loading states.
Clearing live region content immediately. If you clear and refill the region in the same tick, some screen readers announce nothing. Use requestAnimationFrame or a brief setTimeout between clear and fill.
npx claudepluginhub jeffreytse/grimoire --plugin grimoireApplies ARIA roles, states, and properties correctly to enhance assistive technology support for custom widgets. Includes patterns for accessible names, live regions, and state attributes.
Assigns valid ARIA roles, accessible names, and required states to custom UI components that native HTML cannot represent, following WAI-ARIA Authoring Practices.
Provides ARIA roles, states, and properties for interactive components. Use when building custom widgets, fixing screen reader issues, or implementing modals, tabs, accordions, menus, or dialogs accessibly.