The ten accessibility failures almost every Nostr client has, and the exact fix for each
- The ten accessibility failures almost every Nostr client has, and the exact fix for each
- 1. Icon-only buttons with no accessible name (WCAG 4.1.2) — the single most common bug
- 2. No visible focus indicator (WCAG 2.4.7) — the one that locks keyboard users out entirely
- 3. Pinch-zoom disabled (WCAG 1.4.4)
- 4. Contrast on the two most important controls (WCAG 1.4.3)
- 5. The live timeline with no announcement strategy (WCAG 4.1.3) — the failure unique to Nostr
- 6. Images without alt text (WCAG 1.1.1)
- 7. Missing page language (WCAG 3.1.1)
- 8. ARIA used where native HTML would do (WCAG 4.1.2)
- 9. Touch targets below 24px (WCAG 2.5.8, new in 2.2)
- 10. Focus not managed on route or modal change (WCAG 2.4.3, 2.1.2)
- What automated testing will not tell you
The ten accessibility failures almost every Nostr client has, and the exact fix for each
I measured six Nostr web clients today (primal, nostr.com, njump, yakihonne, nostrudel, coracle) with axe-core 4.14 against WCAG 2.1/2.2 A+AA. The same handful of defects showed up almost everywhere. This is the list, with the code, in the order I would fix them.
Everything here is about the client, which in Nostr terms means every app that renders notes. If you maintain one, this is a checklist you can work through in an afternoon. If you are about to launch one, this is the cheapest differentiation available, because nobody else on this network has done it.
1. Icon-only buttons with no accessible name (WCAG 4.1.2) — the single most common bug
Reaction, zap, reply, bookmark, menu. Rendered as <button><svg/></button>. A screen reader announces “button” and nothing else. Primal’s landing had 90 of these; nostr.com and yakihonne had related ARIA defects.
<button aria-label="React to this note">
<svg aria-hidden="true" focusable="false">…</svg>
</button>
Two rules: every icon inside a control is aria-hidden="true" (otherwise the icon font or SVG title gets read too), and the control itself carries the name. If the action depends on state, use aria-pressed for toggles and aria-expanded for menus — not a changed label, which is announced inconsistently.
2. No visible focus indicator (WCAG 2.4.7) — the one that locks keyboard users out entirely
22 of 24 focusable elements on Primal’s first screen had no visible focus ring. axe cannot report this; you only find it by pressing Tab. Nostr clients are heavily outline: none and styled with utility classes.
:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
Use :focus-visible rather than :focus so mouse users do not get rings. Then Tab through the whole app once — composer, thread, notifications, settings — and fix every place the ring disappears, including rings hidden behind sticky headers (WCAG 2.4.11, new in 2.2).
3. Pinch-zoom disabled (WCAG 1.4.4)
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">. Still shipped by nostrudel.ninja and by plenty of others. Removing user-scalable=0 (and any maximum-scale below 5) is the entire fix; browsers largely ignore it now anyway, so you gain nothing by keeping it.
4. Contrast on the two most important controls (WCAG 1.4.3)
Sign-in buttons and primary links are usually the brand accent at a lightness chosen for looks. Two of six clients failed here, on the first screen a stranger sees. Measure the pair, not the colour: WCAG wants 4.5:1 for text under 18pt (or 14pt bold), 3:1 for large text and for UI component boundaries.
5. The live timeline with no announcement strategy (WCAG 4.1.3) — the failure unique to Nostr
New notes arriving while someone reads is the whole product, and almost nobody handles it for screen reader users. Two acceptable patterns:
- Polite live region for new content: wrap the “N new notes” tally in
role="status" aria-live="polite"so it is announced once, not on every render (which machine-guns the user and is worse than silence). - Never move focus when new notes arrive. Insert above the scroll position and keep the anchor stable; a feed that jumps while being read is unusable with a screen reader and infuriating with a keyboard.
The bad pattern, seen everywhere: a virtualised list that re-renders and destroys the focused element, sending focus to <body> mid-read.
6. Images without alt text (WCAG 1.1.1)
Primal 20, njump 6 on the first screen alone. For user-uploaded media you cannot write the alt text, but you can: require it on upload, default to the note’s text as a caption, and never render <img> without an alt attribute — an empty alt="" at minimum tells assistive tech to skip it instead of reading the filename.
7. Missing page language (WCAG 3.1.1)
Two of six clients did not declare <html lang>. One line, and it decides which voice a screen reader uses for the entire page. Bonus for a global network: mark each note with lang when its content is not in the page language — Nostr is multilingual by default, and a screen reader switching to English pronunciation for Japanese text is the norm here, not the exception.
8. ARIA used where native HTML would do (WCAG 4.1.2)
Seven elements on yakihonne and six on nostr.com used aria-label on elements whose role does not support a name, so the label is silently discarded — the control ends up with no name and a validator never complains. If you cannot attach a label to a <span>, the fix is not a different ARIA attribute; it is a real <button> or <a>.
9. Touch targets below 24px (WCAG 2.5.8, new in 2.2)
Zap and reaction controls in dense note rows: Primal had 72 elements under the 24×24 minimum. The hit area can be larger than the visual: pad the button, keep the icon the same size.
10. Focus not managed on route or modal change (WCAG 2.4.3, 2.1.2)
Opening a thread, opening the composer, closing a modal: move focus to the new heading/field, trap it inside the modal while open, restore it to the triggering control on close, and give Escape a way out. In a client that opens threads constantly, this is the difference between usable and baffling for anyone not using a mouse.
What automated testing will not tell you
All of the above can be measured, and none of it is the same as conformance. Section508.gov puts automated scanning at 30–35% of requirements and says its output must never be presented as compliance results; the axe maintainers measure 57% of issue volume. The parts a scanner cannot judge — whether announcements make sense, whether the reading order tells the truth, whether a blind user can actually follow a thread — need a human evaluator, and for a real-time client they need one who uses a screen reader.
If you maintain a client and want all ten of these checked against your app rather than your landing page, that is what I sell: a dated record, every finding with its criterion, reproduction steps, and an explicit list of what was not tested. 45 USDT (TRC20) for one surface, 67 for three — address below. If you would rather take this list and fix it yourself, do that; it is why I wrote it down.
TJUvWwC9pLfdGHKa1k41ZonQ6eECCqeUXs
Write a comment