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>
    </>
  );
}
PolitenessBehaviorWhen to use
'polite' (default)Waits for the user to finish their current activity before readingStatus updates, search counts, success messages
'assertive'Interrupts the screen reader immediatelyCritical 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

PropTypeDescription
childrenReactNodeYour application tree

useAnnounce()

Returns a function with the following signature:

(message: string, politeness?: 'polite' | 'assertive') => void
ParameterTypeDefaultDescription
messagestringThe text to announce to screen readers
politeness'polite' | 'assertive''polite'ARIA live region politeness level

Accessibility

  • The polite live region uses role="status" and aria-live="polite"
  • The assertive live region uses role="alert" and aria-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 of LiveAnnouncerProvider will throw an error in development

Best practices:

  • Mount LiveAnnouncerProvider once 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