---
title: "Screen readers"
description: "The combinations we intend to test, the announcement contract every status change must satisfy, and an honest table of untested cells."
url: "https://opsinjs.pensievelabs.org/accessibility/screen-readers"
source: "https://opsinjs.pensievelabs.org/accessibility/screen-readers.md"
section: "Accessibility"
kind: "accessibility"
reviewed: "2026-09-02"
reviewer: "engineering"
aliases: ["voiceover", "nvda", "jaws", "talkback", "live region"]
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> Nothing is missing from this page. The data simply does not live in
> the prose.

<PageTemplate kind="accessibility" />

## What we guarantee [#what-we-guarantee]

### The announcement contract [#the-announcement-contract]

This is the part of screen-reader support that a design system can genuinely own,
and it is the part most systems leave undefined. Four rules:

1. **Nothing is announced unless a component was asked to announce it.** No
   opsinjs component mounts a live region on your behalf. A library that guesses
   produces either silence where it mattered or a screen reader that will not stop
   talking, and the second is worse: readers turn the feature off.
2. **A value change is `polite`; a safety change is `assertive`.** A metric that
   updates because a background sync completed must not interrupt. An alert that
   escalates to `urgent` while the reader is on the screen must. There is no third
   politeness and no automatic escalation.
3. **The announcement is a sentence, not a fragment.** It says "Blood pressure,
   138 over 84, above your usual range" rather than "138". The fragment is what
   you get when the live region wraps only the number, which is the default
   mistake.
4. **The status word is in the accessible name, not only in the colour.** This is
   the same rule as [Colour independence](./colour-independence.mdx) seen from the
   other side: a screen reader is a permanently greyscale device.

### The intended tested matrix [#the-intended-tested-matrix]

| Screen reader | Platform | Browser | Status      |
| ------------- | -------- | ------- | ----------- |
| VoiceOver     | iOS      | Safari  | Not tested  |
| VoiceOver     | macOS    | Safari  | Not tested  |
| NVDA          | Windows  | Firefox | Not tested  |
| NVDA          | Windows  | Chrome  | Not tested  |
| JAWS          | Windows  | Chrome  | Not tested  |
| TalkBack      | Android  | Chrome  | Not tested  |
| Narrator      | Windows  | Edge    | Not planned |

Mobile combinations are listed first deliberately. A consumer health app is read
on a phone, so VoiceOver on iOS and TalkBack on Android are the primary targets
and desktop is secondary. That ordering is the reverse of the priority most
design systems use.

<Callout>
  Every cell says **Not tested**, and it will keep saying so until somebody has
  actually done it. No component exists to test. This table is a plan, published so
  that its emptiness is visible rather than absent.
</Callout>

## What you own [#what-you-own]

**Every accessible name.** Components supply structure and state; the words come
from you. An icon-only button is unlabelled until you label it.

**Politeness in context.** We can specify that a change is announced politely.
Only your product knows whether five tiles updating at once should produce five
announcements (they should not) or one summary.

**Reading order.** A screen reader reads the DOM. See
[For developers](./for-developers.mdx).

**Spoken numbers.** "138/84 mmHg" is not read the way you say it. Decide what the
spoken form should be and supply it; the formatting rules are on
[Grammar and mechanics](../content/grammar-and-mechanics.mdx).

**Tables and charts.** A chart needs a text equivalent that answers the same
question. See
[Alt text and descriptions](../content/alt-text-and-descriptions.mdx) and
[Accessible charts](../foundations/data-visualisation/accessible-charts.mdx).

## How to check [#how-to-check]

<Steps>
  ### Configure the screen reader properly, once [#configure-the-screen-reader-properly-once]

  Learn the four gestures or keystrokes you need and nothing more. They are next
  item, previous item, next heading and activate. Testing with a screen reader you
  have not configured produces confident wrong conclusions, which is worse than
  not testing.

  ### Read the whole screen linearly [#read-the-whole-screen-linearly]

  Move item by item from the top. &#x2A;*Expected result:** the sequence tells a coherent
  story and every value is accompanied by what it is and what it means.
  **Failure looks like** a run of bare numbers, or an image with no alternative
  text, or a heading announced as a paragraph.

  ### Trigger every state change and listen [#trigger-every-state-change-and-listen]

  Submit the form. Let the value refresh. Escalate the alert. &#x2A;*Expected result:**
  the change is announced once, at the right politeness, as a sentence.
  **Failure looks like** silence after a successful save, or the same announcement
  three times because three regions updated.

  ### Navigate by heading and by landmark [#navigate-by-heading-and-by-landmark]

  Screen-reader users navigate structurally far more than they read linearly. If the
  heading list is not a usable table of contents, the screen has no structure
  regardless of how it looks.

  ### Test with the screen off [#test-with-the-screen-off]

  For a mobile screen reader, turn the screen curtain on. It removes the temptation
  to fill gaps with what you can see.
</Steps>

## Measured results [#measured-results]

<NoDataYet script="scripts/build-registry.mts" />

No screen-reader testing has been performed. When it is, results will be recorded
per component as an `<A11yReport>` block naming the assistive technology, its
version, the browser, the date and the outcome, because an untimestamped
screen-reader result is not reproducible.

## Known gaps [#known-gaps]

* **Every cell in the matrix is untested.**
* **There is no shipped announcement helper,** so the politeness contract above
  is specified and unimplemented.
* **Braille output is not considered anywhere in this system,** and numeric
  formatting for a braille display is a real and unaddressed question.
* **Voice control** (Dragon, Voice Control, Voice Access) is a distinct
  interaction mode that depends on visible labels matching accessible names, and
  we have written no guidance for it.

## Updates to this page [#updates-to-this-page]

<Reviewed />
