Dynamic changes need to be announced, not merely restyled
Aliases: aria-live · live region · role=status · polite assertive · unfocused update
What it is
A change that just happened on screen — a field turning red, a badge going from 0 to 3, a toast sliding in, a load finishing — is a visual transient for sighted users. Assistive technology does not watch pixels and will not speak because a colour or icon swapped. Dynamic changes away from focus must be announced, usually through an aria-live region, role="status" / role="alert", or by rewriting a name or state that will be recomputed. Restyling alone notifies only people looking at the screen.
The problem is how a change is heard when focus is elsewhere — not how to fill name, role, and state in the first place, and not how to take a node off the tree.
Why it happens
A screen reader follows focus and explicit navigation. DOM mutations off focus enter the speech queue only if they sit in a live region, or if they cause the current control's name or state to be recomputed. CSS colour, background-image, decorative pseudo-elements, and a purely visual checkmark do not. aria-live="polite" waits for the current utterance; "assertive" interrupts. role="status" defaults to polite; role="alert" to assertive. The region must already exist in the tree before the change: mount an empty live container, then write text, so the engine observes "this live slot changed". Inserting a brand-new live-bearing subtree is treated by some engines as an ordinary appearance and is not spoken.
Announcement is also bounded by noise. Rapid writes to the same live node may speak only the last one; marking the whole page live turns scrolling and unrelated rerenders into chatter, and users disable live speech. Keep the channel narrow: one status, one error, one completion — not every paint of a region.
Where it stops holding
State changes on the control the user is operating are often spoken with focus already (a switch pressed, a menu expanded); copying them into a live region double-speaks. Long jobs belong in a value or aria-busy ("still working"), not an assertive interrupt every percent. High-frequency streams — chat, tickers, logs — can only be summarised or pulled on gesture, never assertive line by line. Magnifier users who are not running a screen reader will neither see nor hear live speech; the visual change still has to stand on its own. Some mobile engines implement aria-live incompletely, so a critical error cannot live only on that channel.
Applying it
- Write focus-away-must-know messages (form errors, save confirmation, cart count, background completion) into a pre-existing live region; do not only redden a field or swap an icon.
- Default to polite. Use assertive /
alertonly for interruptions that must cut in (session expiry, payment failure). Let only one live region speak at a time. - Mount the empty container first, then change its text. Do not insert the live attribute together with a brand-new subtree. Do not mark a layout root as live.
- How to check: leave focus elsewhere, trigger the change, and listen: is that sentence spoken, is it spoken twice, is it flushed by a later unrelated update? Then turn the screen reader off and confirm the visual channel still works by itself.