What is Live region (aria-live)?

Live region (aria-live): A live region is an element marked with aria-live or role="status" whose content changes are announced by screen readers without moving focus to it.

Screen readers read what has focus and what the user navigates to. They do not notice that a toast appeared in the corner, a search found 24 results, a file finished uploading or a form said "Saved". A live region is the fix: an element marked with aria-live whose text changes are announced automatically, wherever focus is. It is how status messages, counts, errors and progress reach people who cannot see them, and it is also easy to overdo, because every announcement interrupts whatever the user was listening to.

Politeness and roles

  • `aria-live="polite"` waits until the screen reader is idle. Use it for nearly everything: confirmations, result counts, loading and loaded, copied.
  • `aria-live="assertive"` interrupts immediately. Reserve it for errors that stop the user's task, such as a session that expired.
  • `role="status"` is a polite region with aria-atomic="true", which reads the whole region rather than only the changed words. `role="alert"` is the assertive equivalent. `role="log"` is polite and suits chat transcripts, where only new lines are read.
  • `aria-atomic` controls whether the whole region or only the change is read, and `aria-relevant` can limit announcements to additions, removals or text changes; the defaults are right for most uses.

Rules that make it work

  • The region must exist before the message. Screen readers watch regions already in the accessibility tree; inserting a new element that already contains text is often missed. Render the empty region first and change its text later, which is why toast systems mount one container at app start.
  • Change the text, do not re-create the node. Replacing a message with identical text may not announce; clear it first.
  • One or two global regions are enough. Dozens of regions compete and the result is noise.
  • Do not announce every keystroke or every percent. Announce the result of typing after a pause, and progress at milestones or on completion.
  • Keep it short and specific. "3 results for invoice" beats "The search has completed and results are now displayed below."
  • Hide it visually when the message is redundant with what sighted users see, using the visually hidden pattern, and keep the visible message when both groups need it.

How to build one in React

A small announcer hook renders one polite region and lets any component push messages:

tsx
"use client"
import * as React from "react"

export function useAnnouncer() {
  const [message, setMessage] = React.useState("")
  const announce = React.useCallback((text: string) => {
    setMessage("")
    window.setTimeout(() => setMessage(text), 50)
  }, [])
  const region = (
    <div role="status" aria-live="polite" className="sr-only">
      {message}
    </div>
  )
  return { announce, region }
}

// const { announce, region } = useAnnouncer()
// announce(results.length + " results")
// {region}  rendered once, always mounted

The brief clear before setting the text forces an announcement even when the same message repeats. MiniDev's Toast uses role="status" for most tones and role="alert" only for errors, Spinner is a status region named by its label, and Banner announces itself as status when it appears. The ARIA entry covers the attributes around these.

ToastuiToast notifications with a toast() API for success, error, warning and promise states. The Toaster stacks cards, fans them out on hover and pauses timers.npx shadcn@latest add @minidev/toastSpinneruiLoading spinner in sm, default and lg sizes with role status and an accessible Loading label. Drop it into buttons, cards or full page states in a Tailwind app.npx shadcn@latest add @minidev/spinner