# Status messages that are shown but never announced

Source: https://easeweb.dev/learn/status-messages
Topics: Live updates > Status messages are announced (WCAG 4.1.3)
The fix: Write the message into a status region that is already on the page and empty. Either keep a short status line next to the thing it reports on, or keep the toasts but render them inside a toast container with role="status" that is there from page load. Don't move focus to the message.
Test with: screen reader, keyboard, zoom
References: https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA22 https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/status_role https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-live

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A notes app saves a draft a moment after you stop typing. When it saves, a small toast slides in at the bottom of the page: "Draft saved". Three seconds later it is gone. A sighted user glances at it and carries on. A screen reader user types a paragraph, pauses, and hears nothing. They don't know whether the draft was saved, whether it is safe to close the tab, or whether the save failed, so they look for a save button that isn't there, or copy the text somewhere else to be safe.

The developer did add `aria-live="polite"` to the toast. It still says nothing.

## Why it fails

"Draft saved" is a status message: it reports the result of something the person did, without taking focus. Success Criterion 4.1.3 asks that such messages can be presented to assistive technology through role or properties, so that they are announced without moving focus. A live region does that, but only for changes the browser sees inside a region it already knows about. Here the script creates the toast element, its text and its `aria-live` in one step and appends it to the page. To the browser that is a new region arriving with its content, not a change to an existing one, and most screen readers stay silent. The visible message and the spoken one have come apart.

## Who is affected

The toast is placed for someone looking at the whole page, so anyone who isn't, because they are listening or because they are zoomed in on the text they are writing, misses it.

## What automation and AI miss

A scan finds `aria-live` on the toast and passes it; it can't tell that the region was born with its message. A scan of the page when no toast is showing sees no region at all. AI assistants often write exactly this pattern, because the attribute is present and the code looks complete. Others move focus to the toast so that it is read, which pulls the caret out of the text in the middle of a sentence. Only a screen reader, used while typing, shows whether anything is said.

## Before: a toast created with its own aria-live

```html
<label for="note">Note</label>
<textarea id="note"></textarea>
```

```js
function showToast(text) {
  const toast = document.createElement('div');
  toast.className = 'toast';
  toast.setAttribute('aria-live', 'polite');
  toast.textContent = text;
  document.body.append(toast);
  setTimeout(() => toast.remove(), 3000);
}

note.addEventListener('input', debounce(async () => {
  await saveDraft(note.value);
  showToast('Draft saved');
}, 800));
```

The region and its words arrive together and leave together, so the browser never sees the text change inside a region it was watching. The message is on screen, and never announced.

## After: a status line beside the editor

```html
<label for="note">Note</label>
<textarea id="note"></textarea>
<p id="note-status" role="status"></p>
```

```js
note.addEventListener('input', debounce(async () => {
  try {
    await saveDraft(note.value);
    status.textContent = `Draft saved at ${timeNow()}.`;
  } catch {
    status.textContent = 'Draft not saved. Check your connection.';
  }
}, 800));
```

The `p` with `role="status"` is in the HTML from the start and empty, so the browser is watching it when its text changes. The screen reader finishes what it is saying, then reads "Draft saved at 10:42." The caret stays in the text. The line stays under the editor, where both a magnifier user and a sighted user are already looking, and the time tells people the save is recent.

## After: toasts inside a status container that is always there

```html
<label for="note">Note</label>
<textarea id="note"></textarea>
<!-- Once, in the page layout, outside any component that mounts and unmounts -->
<div id="toasts" class="toasts" role="status"></div>
```

```js
function showToast(text) {
  const toast = document.createElement('div');
  toast.className = 'toast';
  toast.textContent = text;
  toasts.append(toast);
  setTimeout(() => toast.remove(), 5000);
}
```

The toast keeps its look and its place, but now it is added inside a region that was on the page from the start, so adding it is a change the browser announces. The toast itself has no `aria-live`. This is the smallest change when a site already has a toast system: one empty container in the layout, and one line moved. The container must stay in the page and visible to assistive technology even when it is empty; don't hide it with `display: none` between toasts.

## Which option to use

Both announce "Draft saved" without moving focus. Use the status line beside the editor: it is the same amount of code, and it gives up nothing, because the message also stays where people are looking and doesn't vanish before a magnifier user can find it. The toast container is the right choice when a site already shows its messages as toasts and changing that is out of reach; it fixes every toast at once. With toasts, give people enough time to read them, and keep anything they must act on out of them.

## Implementation decisions

**The region must exist first.** In a component framework, render the region in the layout or in the editor's own markup, never inside an `{#if}` or a conditional render that appears with the message. A region added with its message is the same failure in different code.

**Hidden regions don't speak.** A live region inside `display: none`, `visibility: hidden` or `aria-hidden="true"` is not announced, and in some browsers a region that was hidden and is then shown along with its text is treated as new. To hide the status line visually and still have it read, use a visually hidden class, not `display: none`.

**The role is the live region.** `role="status"` already means `aria-live="polite"` and `aria-atomic="true"`. A `div` with `aria-live="polite" aria-atomic="true"` and no role works the same way. Don't add `aria-live="assertive"` or `role="alert"` to routine saves; it cuts into what the person is listening to every time they pause.

**Say it at the right moment.** Announce after a save finishes, not on every keystroke, and not "Saving…" every few seconds. If the same words are written twice, some screen readers stay silent the second time; the time in "Draft saved at 10:42" changes the text, or clear the region before writing.

**Failure needs words too.** "Draft not saved" is a status message as well. If people lose work when they leave, also show it next to the editor until saving works again.

**A close button.** A toast can have a button to dismiss it, but a button inside the live region may be read with the message ("Draft saved, Dismiss"). That is acceptable: keep its name short. Don't give the toast a live region of its own to avoid it; that brings back the failure in "Before". Don't rely on the button alone to remove the toast: it should still go by itself after enough time to read it.

**Not fixes.** Moving focus to the toast does get it read, but it takes the caret out of the text, and it is not what 4.1.3 asks for. The `<output>` element maps to the status role, but screen readers announce its changes less reliably than an explicit `role="status"`.

## Verify the fix

1. Inspect the DOM before typing: the status line, or the toast container, is present, empty, has `role="status"`, and is not hidden with `display: none` or `aria-hidden`.
2. With a screen reader on, type in the note and pause: "Draft saved" is announced once, after other speech has finished, and the caret stays in the text.
3. Type and pause again, then make the save fail (go offline in developer tools): each save is announced again, and the failure is announced in words.
4. Zoom the page to 400% or use a screen magnifier on the editor: the message can be found and read near the editor, or the toast stays long enough to pan to it.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the toast component or the page layout changes.

## Limits

Live regions behave differently across screen readers and browsers, especially for regions that are moved, hidden or inside dialogs. This pattern covers one kind of message on one page. Loading, search results, form results and urgent alerts are topics of their own. Test your real messages with the screen readers your users use.
