@a11y_craft/aria-announce
v1.0.2
Published
Tiny, zero-dependency aria-live region manager for screen reader announcements (VoiceOver, NVDA, JAWS) — works with React, Vue, Svelte, and vanilla JS
Maintainers
Readme
@a11y_craft/aria-announce
Tiny, zero-dependency
aria-liveannouncer for screen readers — works with React, Vue, Svelte, Angular, and vanilla JS.
Screen reader support: VoiceOver (macOS/iOS) · NVDA · JAWS · TalkBack (Android) · Narrator (Windows)
Framework support: React · Vue · Svelte · Angular · vanilla JS · any DOM environment
Why
aria-live regions are the standard way to make dynamic content accessible to screen reader users — but getting them right is tricky:
- Setting text on an existing live region doesn't always re-trigger screen readers
- Sending messages too fast causes announcements to be dropped
- Duplicate messages within a short window create noise
- Most existing packages are framework-specific, heavy, or abandoned
aria-announce solves all of this in ~1 KB, with no dependencies.
Install
npm install @a11y_craft/aria-announce
# or
yarn add @a11y_craft/aria-announce
# or
pnpm add @a11y_craft/aria-announceQuick Start
import { announce } from '@a11y_craft/aria-announce';
announce('Your file has been saved.');That's it. No setup, no providers, no configuration required.
API
announce(message, options?)
The main function. Queues a message for announcement.
announce('3 results found');
announce('Session expiring in 1 minute', { politeness: 'assertive' });Options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| politeness | 'polite' \| 'assertive' | 'polite' | Politeness level of the announcement |
| delay | number (ms) | 100 | Delay before announcing — helps screen readers catch the update |
| dedupeMs | number (ms) | 500 | Suppress duplicate messages within this window |
announce.polite(message)
Shorthand for polite announcements (non-interrupting).
announce.polite('Form submitted successfully.');announce.assertive(message)
Shorthand for assertive announcements (interrupts the screen reader immediately).
announce.assertive('Error: Please fill in all required fields.');Use assertive sparingly — it interrupts whatever the user is currently hearing.
announce.clear()
Clears the queue and empties all live regions.
announce.clear();announce.destroy()
Removes the aria-live DOM elements entirely. Useful for cleanup in SPAs or tests.
announce.destroy();Usage Examples
Vanilla JS
import { announce } from '@a11y_craft/aria-announce';
document.querySelector('#save-btn').addEventListener('click', async () => {
await saveData();
announce('Changes saved successfully.');
});
document.querySelector('#delete-btn').addEventListener('click', () => {
announce.assertive('Item deleted.');
});React
import { announce } from '@a11y_craft/aria-announce';
function SaveButton() {
const handleSave = async () => {
await save();
announce('Draft saved.');
};
return <button onClick={handleSave}>Save</button>;
}No provider needed. Works in any component, anywhere in the tree.
// In a toast/notification system
function showToast(message: string, type: 'info' | 'error') {
if (type === 'error') {
announce.assertive(message);
} else {
announce.polite(message);
}
}Vue
<script setup>
import { announce } from '@a11y_craft/aria-announce';
async function handleSubmit() {
await submitForm();
announce('Form submitted successfully.');
}
</script>
<template>
<button @click="handleSubmit">Submit</button>
</template>Svelte
<script>
import { announce } from '@a11y_craft/aria-announce';
async function handleUpload() {
await upload();
announce('File uploaded.');
}
</script>
<button on:click={handleUpload}>Upload</button>With a loading state
import { announce } from '@a11y_craft/aria-announce';
async function fetchResults(query: string) {
announce('Loading results...');
const results = await search(query);
announce(`${results.length} results found for "${query}".`);
}How It Works
On the first call, aria-announce creates two visually hidden aria-live elements in the document body — one polite, one assertive:
<div aria-live="polite" aria-atomic="true" style="...visually hidden..."></div>
<div aria-live="assertive" aria-atomic="true" style="...visually hidden..."></div>When you call announce(), it:
- Checks for duplicates (suppresses repeated messages within
dedupeMs) - Queues the message with the chosen politeness level
- Clears the region, then sets the new message (forces screen readers to re-announce)
- Processes the queue sequentially so rapid calls don't drop announcements
Screen Reader Compatibility
aria-announce is tested and works with the major screen readers:
| Screen Reader | Platform | Notes |
|---------------|----------|-------|
| VoiceOver | macOS, iOS | Both polite and assertive |
| NVDA | Windows | Both polite and assertive |
| JAWS | Windows | Both polite and assertive |
| TalkBack | Android | aria-live supported |
| Narrator | Windows | Both polite and assertive |
WCAG Compliance
aria-announce is designed to align with WCAG 2.2 Success Criterion 4.1.3 (Status Messages):
- Uses
aria-livewith appropriate politeness levels - Sets
aria-atomic="true"so the full message is read, not just changed text - Avoids focus movement for status messages
SSR / Server-Side Rendering
aria-announce is a no-op in server environments. All calls are safely ignored when document is not available.
TypeScript
Full TypeScript support with exported types:
import { announce } from '@a11y_craft/aria-announce';
import type { AnnounceOptions, AnnounceFunction } from 'aria-announce';
const options: AnnounceOptions = {
politeness: 'assertive',
delay: 50,
dedupeMs: 1000,
};
announce('Upload complete.', options);Browser Support
Works in all modern browsers. Requires a DOM environment (not SSR).
License
MIT © Nidhi Gajera
