In a component library, why provide one shared live-region announcer instead of letting each component create its own live region?
answer
- messages that do not move focus
- the region must already be there
- many regions talk over each other
- one queue, deduplicated
- polite by default
basics
~10 sLive regions created on the fly are often not announced, and many regions talk over each other. One announcer mounted up front queues, deduplicates and throttles status messages, so every component announces reliably.
solid answer
~50 sWCAG 2.2 success criterion **4.1.3 Status Messages** (Level AA) requires that status messages — *Draft invoice saved*, *12 bills shown*, *3 transactions matched* — can be presented by assistive technology **without moving focus**, which on the web means putting them in a live region. Letting each component make its own breaks in practice: a region created at the moment of the message is often not announced, because assistive technologies commonly track changes to regions already present; several regions updating together interrupt or drop each other; and three instances of one filter component announce the same count three times. A shared **announcer** — polite and assertive regions mounted once at the app root, behind one call such as `announce(message, priority)` — queues messages, drops duplicates, throttles rapid updates like typing in a filter, and clears itself. On native mobile the same service wraps the platform's announcement call, so components use one API everywhere.
code
pseudocode · 19 linesannouncer = { queue: [], lastText: null, lastTime: 0 }
function announce(text, priority = POLITE):
if text is empty:
return
if text == announcer.lastText and now() - announcer.lastTime < 1000 ms:
return -- duplicate inside the window: drop it
announcer.lastText = text
announcer.lastTime = now()
announcer.queue.push({ text, priority })
scheduleFlush()
function flush():
next = announcer.queue.shift()
if next is none:
return
region = next.priority == ASSERTIVE ? assertiveRegion : politeRegion
region.setText(next.text) -- region existed since app start
after 500 ms: region.setText(""); scheduleFlush()go deeper
Recall what a status message is and that WCAG 2.2 criterion 4.1.3 requires it to be announced without moving focus.
Explain why pre-mounted regions, one queue and deduplication make announcements reliable, and when to choose polite or assertive.
Show how you would diagnose missing or doubled announcements across products and migrate scattered live regions to one announcer.
Set the rules for what the system announces at all, balancing a chatty product against silent failures, across web and native.
## The problem a status message solves A **status message** tells the user about the result of an action or a change in progress without moving their focus: *Draft invoice saved*, *12 bills shown*, *Receipt attached*, *3 transactions matched*. A sighted user glances at a toast or a counter. A screen-reader user hears nothing unless the message is exposed in a way assistive technology will announce. WCAG 2.2 success criterion **4.1.3 Status Messages** (Level AA) requires that such messages *can be programmatically determined through role or properties such that they can be presented to the user by assistive technologies without receiving focus*. On the web that means a **live region**: an area whose content changes are announced. On native platforms it means the platform's announcement mechanism. ## Why per-component regions fail - **Created too late.** A region inserted at the same moment as its message is often not announced, because assistive technologies commonly watch regions that were already present and pick up *changes* to them. A component that creates its region only when it has something to say races this. - **Talking over each other.** On the reconciliation screen of a small-business accounting product, a matching action, an autosave and a filter can all update in the same second. Separate regions interrupt one another or get dropped, and which survives depends on timing. - **Duplicated.** A filter component used in three panels announces *12 bills shown* three times. - **Inconsistent priority.** One team marks every message urgent; another marks errors as low priority. Users cannot trust either. ## What a shared announcer does 1. **Mounts early.** A polite region and an assertive region exist from app start, so every later change is a change to a region already present. 2. **Exposes one call.** Components — and consumers — call `announce(message, priority)`; nobody touches the regions directly. 3. **Queues.** Messages are spoken in order, one at a time, rather than overwriting each other. 4. **Deduplicates and throttles.** Identical messages within a short window collapse into one; rapid updates, such as a result count while typing, settle before announcing. 5. **Clears.** After a message is read, the region is emptied so the same text can be announced again later. | Priority | Use for | Accounting-product example | |---|---|---| | Polite (default) | results, confirmations, progress | *Draft invoice saved*, *12 bills shown* | | Assertive | time-sensitive problems the user must hear now | *Bank connection lost; transactions are not syncing* | Polite messages wait until the screen reader finishes what it is saying; assertive ones interrupt. Defaulting to polite and requiring a reason for assertive keeps the product from becoming noisy. ## Wording the message An announcement is heard once, out of visual context, so the library's guidance should shape the text as well as the timing: - **Self-contained.** *Draft invoice saved* rather than *Saved*: the listener may not know which of several drafts or panels the message is about. - **Specific.** Include the count or the object — *3 transactions matched*, not *Matching complete*. - **Short.** Every word delays whatever the user was about to hear next. - **Not a repeat.** If the same text is already announced through focus or a component's state, announcing it again doubles it. - **Translatable.** Messages are user-facing strings and go through the same translation path as visible text. ## What does not go through the announcer The criterion's own guidance draws two boundaries: - **Changes of context.** A dialog that opens and takes focus is already announced because focus moved; it is not a status message. - **A component's own state changes.** An accordion section expanding is conveyed through that component's state under **4.1.2 Name, Role, Value**; announcing it again duplicates it. The same guidance warns about making an application too *chatty*. An announcer makes announcing easy, so the library's usage guidance must also say when *not* to announce. ## Across platforms Native mobile platforms provide a call to post an announcement to the screen reader. A cross-platform system gives every platform's library the same announcer contract — message, priority, deduplication — and implements it with each platform's mechanism. Components then announce the same messages in the same situations everywhere.
- A filter updates its result count on every keystroke. How should it use the announcer?Announce once the input settles, not per keystroke — the announcer's throttling or a short debounce in the component. Otherwise a screen-reader user hears a stream of counts, each interrupting the last. The final count, politely, is the useful message.
- When is assertive priority justified?When the user must hear the message now to avoid harm or wasted work: a lost connection, a failed save, a session about to expire. Routine confirmations and result counts are polite. Requiring a stated reason for assertive keeps teams from marking everything urgent.
saying these in an interview costs you the question
- Status messages should move focus so screen readers read them.
- A live region can be created at the moment the message appears.
- Every content change on a page should be announced.
- Each component should own its own live region for isolation.
- Assertive is the safe default so no message is missed.