Live Announcer
The Live Announcer provides a React context and hook for sending programmatic announcements to screen readers. Wrap your application once with LiveAnnouncerProvider, then call announce() from any component using the useAnnounce() hook — no prop drilling required.
Use it to announce dynamic changes that happen outside the user's current focus: search result counts, toast notifications, form validation errors, loading completion, and similar events.
Installation
npm install @libretexts/davis-react
Setup
Add LiveAnnouncerProvider once at your application root. It renders two visually hidden live regions into the DOM that persist for the lifetime of the app.
import { LiveAnnouncerProvider } from '@libretexts/davis-react';
export default function App() {
return (
<LiveAnnouncerProvider>
<YourApp />
</LiveAnnouncerProvider>
);
}
Basic Usage
Call useAnnounce() in any component that is a descendant of LiveAnnouncerProvider. The returned function accepts a message string and an optional politeness level.
import { useAnnounce } from '@libretexts/davis-react';
import { useEffect } from 'react';
function SearchResults({ count }: { count: number }) {
const announce = useAnnounce();
useEffect(() => {
announce(`${count} results found`);
}, [count]);
return <div>...</div>;
}
Polite vs Assertive
The second argument to announce() controls the ARIA politeness level.
import { useAnnounce } from '@libretexts/davis-react';
function NotificationExample() {
const announce = useAnnounce();
return (
<>
<button onClick={() => announce('3 new messages')}>
Check messages
</button>
<button onClick={() => announce('Session expired. Please log in again.', 'assertive')}>
Trigger error
</button>
</>
);
}
| Politeness | Behavior | When to use |
|---|---|---|
'polite' (default) | Waits for the user to finish their current activity before reading | Status updates, search counts, success messages |
'assertive' | Interrupts the screen reader immediately | Critical errors, session expiry, urgent alerts |
Use assertive sparingly — interrupting the user mid-sentence is disruptive. Reserve it for errors that require immediate attention.
Form Validation Example
import { useAnnounce } from '@libretexts/davis-react';
function LoginForm() {
const announce = useAnnounce();
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
const result = await login();
if (result.error) {
announce('Login failed: incorrect password', 'assertive');
} else {
announce('Login successful. Redirecting to dashboard.');
}
}
return (
<form onSubmit={handleSubmit}>
<input type="email" name="email" />
<input type="password" name="password" />
<button type="submit">Log in</button>
</form>
);
}
Re-announcing the Same Message
The same message string can be announced multiple times. The announcer clears the live region and re-sets it via requestAnimationFrame to ensure the screen reader detects the change even when the message is identical to the previous one.
function RetryExample() {
const announce = useAnnounce();
function handleRetry() {
// This will be announced every time, even if the message is the same
announce('Retrying connection...');
}
return <button onClick={handleRetry}>Retry</button>;
}
Props & API
LiveAnnouncerProvider
| Prop | Type | Description |
|---|---|---|
children | ReactNode | Your application tree |
useAnnounce()
Returns a function with the following signature:
(message: string, politeness?: 'polite' | 'assertive') => void
| Parameter | Type | Default | Description |
|---|---|---|---|
message | string | — | The text to announce to screen readers |
politeness | 'polite' | 'assertive' | 'polite' | ARIA live region politeness level |
Accessibility
- The polite live region uses
role="status"andaria-live="polite" - The assertive live region uses
role="alert"andaria-live="assertive" - Both regions have
aria-atomic="true"so the full message is always read on each update, not just the changed portion - Both regions are visually hidden using the SR-only clip pattern but are always present in the DOM — screen readers track live regions from page load, so late-mounted regions can miss announcements
- Announcements auto-clear after 7 seconds to prevent stale text from being re-read when focus returns to the region
- Calling
useAnnounce()outside ofLiveAnnouncerProviderwill throw an error in development
Best practices:
- Mount
LiveAnnouncerProvideronce at the application root — nesting multiple providers is unnecessary - Keep announcement messages concise — screen readers will read the full string and a long message delays other announcements
- Do not announce every keystroke; batch or debounce announcements for frequently changing values (e.g., filter results)
- Prefer
'polite'for all status messages; only use'assertive'for errors that require immediate attention