Accessibility QA
Screen Reader Testing
Run this pass before each release to check how forms, dialogs, and data views sound, not just how they look.
Test matrix
Cover at least one desktop and one mobile screen reader.
| Platform | Screen reader | Browser |
|---|---|---|
| macOS | VoiceOver | Safari |
| Windows | NVDA | Chrome or Firefox |
| iOS | VoiceOver | Safari |
| Android | TalkBack | Chrome |
Release Checklist
Mark each item during manual QA.
Starter shortcuts
Enough to navigate a page by headings, landmarks, and forms.
| Keys | Expected behavior | Where |
|---|---|---|
| CtrlOptionCmdH | Next heading | VoiceOver (macOS) |
| H | Next heading | NVDA browse mode |
| D | Next landmark | NVDA browse mode |
| F | Next form field | NVDA browse mode |
| InsertSpace | Toggle browse and focus mode | NVDA |
Announced search results pattern
Use aria-live="polite" for async updates that should be spoken without interrupting what the user is doing.
import { useEffect, useState } from "react"import { Input } from "@glinui/ui"
export function SearchAnnounce() { const [query, setQuery] = useState("") const [count, setCount] = useState(0)
useEffect(() => { const timeout = setTimeout(() => { // Replace with real search call setCount(query ? 12 : 0) }, 120) return () => clearTimeout(timeout) }, [query])
return ( <div className="space-y-2"> <Input aria-label="Search components" placeholder="Search components" value={query} onChange={(event) => setQuery(event.target.value)} /> <p aria-live="polite" className="text-sm text-muted"> {count} results </p> </div> )}