# What screen readers hear, and the WCAG numbers

> Announce changes with the quietest mechanism that works, hide things the right way, and cite the WCAG 2.2 thresholds that turn an opinion into a finding.

Module: Components and states · Level: intermediate · 8 min · https://school.agents.org.in/design/components/announcements-and-wcag

Below is a transcript of a screen reader (Software that reads the interface aloud (or to a braille display) for people who can't see the screen, like VoiceOver on Apple devices and NVDA on Windows) reading an invoice aloud. It presses Save by itself once a lap; flip the mode, or press Save yourself mid-sentence.

*[Interactive demo: Polite waits for the sentence to end. Assertive interrupts it.]*

A sighted person sees "Saved" appear. A screen-reader user hears it only if something announces it, and how it is announced decides whether it helps or barges in.

## The ladder: stop at the first rung that fits

  **Step: Focus moves there anyway.** An opened dialog, the first invalid field after Submit. Moving focus *is* the announcement. Add nothing.

  **Step: It belongs to one control.** A field's error, a character count: `aria-describedby` on the input, so it is read with the field.

  **Step: Not urgent, not tied to a control.** A toast, "Saved", "12 results", "Loading…": a polite live region (A part of the page that screen readers watch and read aloud when its text changes. role="status" waits politely; role="alert" interrupts), `role="status"`.

  **Step: Urgent, not tied to a control.** A form-level failure, a session about to expire: `role="alert"`, and nothing else gets it.

`role="status"` is the polite one; `role="alert"` is assertive and interrupts. **Default to polite.** The common over-correction is an assertive success toast: "Saved" now cuts off whatever the user was listening to.

## Making the region actually speak

Live regions are fussy. Three rules make them reliable:

- **Render the region empty first, then change its text.** A region that mounts already holding its message is announced inconsistently.
- **Keep messages short and whole.** The region re-reads everything inside it on each change.
- **Never move focus to a toast.** Announce it; leave focus where the person is working.

  **Before (avoid):**

```tsx
{msg && <div role="status">{msg}</div>}
```

  **After (do this):**

```tsx
<div role="status" className="sr-only">{msg}</div>
```

For loads, put `aria-busy="true"` on the updating area, say "Loading…" politely, then the outcome: "Loaded, 12 results".

## Hiding, two opposite ways

| You want | Use |
|---|---|
| Invisible on screen, still read aloud | `.sr-only`: 1px box, `clip-path: inset(50%)`, `white-space: nowrap`. One pixel, not zero: some readers skip empty boxes |
| Visible on screen, not read aloud | `aria-hidden="true"` on decorative icons and visual duplicates |

Never put `aria-hidden` on or above a button, link or input. Tab still lands there, and the reader says nothing exists. To hide something interactive, use `inert`.

## Alt text by purpose

| The image | `alt` |
|---|---|
| Decorative, or repeats nearby text | `alt=""`: present and empty |
| Informative | What it adds: "Ticket QR code" |
| The image is the button | The action: "Search", not "magnifying glass" |
| A chart | A one-line summary, with the data in a table nearby |

A missing `alt` is worse than an empty one: the reader falls back to the file name.

## The numbers that make a finding

In a review, "this feels inaccessible" is an opinion. A WCAG (The Web Content Accessibility Guidelines, the standard most laws point to. Its contrast rule is the familiar 4.5:1 for body text and 3:1 for large text) criterion with its number is a finding. WCAG 2.2 Level A and AA are the rules products are held to:

| Criterion | The rule |
|---|---|
| 1.4.3 Contrast | Text 4.5:1; large text (24px, or 18.67px bold) 3:1 |
| 1.4.11 Non-text contrast | Input borders, icons, focus rings: 3:1 against every adjacent color |
| 1.4.1 Use of color | Status never by color alone |
| 2.5.8 Target size | 24×24px, or spaced so a 24px circle on it touches no other target |
| 2.5.7 Dragging | Every drag has a single-click alternative |
| 2.4.11 Focus not obscured | The focused element is never fully hidden by sticky chrome |
| 1.4.4 / 1.4.10 | Survives 200% text; works at 320px wide with vertical scroll only |
| 2.2.2 Pause, stop, hide | Anything moving or updating for more than 5 seconds has a pause |
| 1.4.2 Audio | Nothing autoplays with sound |

Disabled controls and logos are exempt from contrast. The 44px touch target and "one `<h1>` per page" are good advice but not AA, so they are recommendations, never the top severity.

> **Fixes the numbers imply.** Focus under a sticky header: `scroll-padding-block-start` equal to the header height. Zoom: never `maximum-scale=1`; text boxes take `min-height`, not `height`. Windows High Contrast drops box-shadows, so a shadow ring needs a `1px solid transparent` border underneath. A toast with an action stays at least 5 seconds and pauses on hover.

*(Measure contrast against what the text actually sits on, not the page background.)*

**Announcements and a WCAG check (paste into your agent):**

```text
Audit this screen for screen-reader output and WCAG 2.2 AA:
- Pick the quietest announcement per change: focus move, aria-describedby, role="status", role="alert" (failures that block only).
- Live regions are always in the DOM and start empty; never focus a toast.
- Decorative icons aria-hidden; nothing aria-hidden contains a focusable element.
- Alt text by purpose; empty alt for decoration.
- Report each failure as: element, WCAG criterion number, measured value, threshold. Don't change colors unless I ask.
```

**Check yourself:** A 'Changes saved' toast should be announced with…

- [ ] role="alert", so nobody misses it
- [x] role="status", a polite region that waits for the current speech
- [ ] Moving keyboard focus onto the toast
- [ ] Nothing; screen readers read toasts automatically

*Why:* Success is not urgent and not tied to a control, so it gets a polite status region. Alert is for failures that block the task, and focus should never jump to a toast.

## In the skill

- https://school.agents.org.in/skill/components/screen-reader-announcements.md
- https://school.agents.org.in/skill/components/wcag-thresholds.md
