Минулого року я три дні аудирував checkout-потік, який мав рейтинг 4.8/5 у дизайн-ревʼю Figma. Візуально - бездоганно. Але користувачі, що працюють лише з клавіатурою, не могли перейти в поле промокоду: там був <div> з обробником click, без tabindex, без role. Користувачі скринридерів чули 'без назви' на кожному інпуті, бо дизайнер використав placeholder як лейбл, а розробник скопіював паттерн. Повідомлення про помилки відображались червоним, але не були програмно повʼязані з полями. Кнопка 'Оформити замовлення' перехоплювала Enter, але не Space. Кожен із цих багів був невидимий для всіх, хто використовує мишу.
Це стійкий паттерн проблем із доступністю: вони невидимі для більшості команди і повністю блокують конкретну групу користувачів. У цьому пості я розбираю, як скринридери реально парсять DOM, що вимагає WCAG 2.2 у конкретних термінах реалізації, які паттерни клавіатурної навігації ламаються найчастіше і як побудувати тестовий процес, що знаходить ці проблеми раніше за користувачів.
Частина I - Хто використовує асистивні технології і чому це важливіше за compliance
- Користувачі скринридерів: За даними ВООЗ, 285 мільйонів людей у світі мають порушення зору. Частки ринку скринридерів: JAWS (38%), NVDA (31%), VoiceOver (iOS/macOS, 18%), TalkBack (Android, 7%). NVDA - безкоштовний, VoiceOver вбудований у кожен пристрій Apple, TalkBack - в Android. Ці користувачі вже є у вашій аудиторії.
- Користувачі лише з клавіатурою: Порушення моторики стосуються близько 2 мільйонів американців, що використовують альтернативні пристрої вводу - перемикачі, керування диханням, трекери погляду. Усі вони емулюють клавіатурний ввід на рівні ОС.
- Когнітивні та навчальні порушення: Дислексія стосується 15–20% населення, СДУГ - 8–10% дорослих. Чітка типографічна ієрархія, передбачувані паттерни навігації та видимі стани фокусу допомагають усім цим групам.
- Правовий ризик: ADA (США), EN 301 549 (ЄС) та Equality Act (Великобританія) встановлюють правові вимоги до цифрової доступності. Кількість ADA-позовів проти вебсайтів перевищила 4600 у 2023 році (UsableNet).
- Тимчасові та ситуативні обмеження: Зламана рука, яскраве сонце, гучне середовище - контраст, клавіатурне керування та субтитри допомагають усім у субоптимальних умовах.
Частина II - Як скринридери реально парсять DOM
Ментальна модель, що прибирає більшість ARIA-помилок: скринридери читають не HTML - вони читають дерево доступності. Це паралельна структура, яку браузер будує з DOM та передає через платформенні API. Кожен вузол має чотири ключових властивості: role, name, state і value.
- Role: Визначається тегом HTML-елемента або перевизначається атрибутом
role.<div>без role - 'generic' контейнер: скринридер прочитає його текст у режимі перегляду, але користувачі не знайдуть його за швидкими клавішами. - Name: Доступне імʼя - те, що скринридер оголошує при фокусуванні. Джерела за пріоритетом:
aria-labelledby,aria-label, вміст елемента, повʼязаний<label>. Placeholder - НЕ джерело імені. - State: Розгорнуто/згорнуто (
aria-expanded), відмічено (aria-checked), невалідно (aria-invalid). Стан має оновлюватись програмно при зміні. - Режим перегляду vs інтерактивний режим: NVDA/JAWS за замовчуванням у режимі перегляду - стрілки переміщують віртуальний курсор по дереву доступності. При фокусуванні текстового інпуту переходять в інтерактивний режим. Нативна семантика критична -
<div>зonclickне перемикає режим.
Частина III - Клавіатурна навігація: паттерни, що ламаються найчастіше
- Порядок табуляції та порядок DOM: CSS flexbox і grid дозволяють візуально переставляти елементи без зміни порядку DOM - це розриває відповідність між візуальним та клавіатурним порядком. Порядок джерела DOM має відповідати передбаченому порядку читання.
- Focus trap у модальних вікнах: При відкритті модала фокус має бути заблокований всередині. Табуляція циклічно переміщується лише по фокусованих елементах модала. При закритті фокус повертається до тригера. HTML
<dialog>обробляє це нативно. - Клавіатурні паттерни для кастомних віджетів: ARIA APG визначає очікувану поведінку клавіатури. Кастомний дропдаун:
Enter/Spaceвідкриває меню, стрілки навігують по опціях,Enterвибирає,Escapeзакриває. Реалізація половини паттерну гірша за відсутність реалізації. - Skip navigation links: Перший фокусований елемент на кожній сторінці - посилання 'Перейти до основного вмісту'.
<a href='#main-content' class='sr-only focus:not-sr-only'>Перейти до основного вмісту</a>плюс<main id='main-content' tabindex='-1'>. - Видимість фокусу: WCAG 2.2 SC 2.4.11 вимагає, щоб сфокусований елемент не був повністю прихований sticky-заголовками. Використовуйте
scroll-margin-topта:focus-visible.
Частина IV - WCAG 2.2: критерії, що реально зустрічаються в аудитах
- SC 1.1.1 Нетекстовий контент (Level A): Кожне зображення має мати текстову альтернативу. Інформаційні:
altописує вміст. Декоративні:alt=''. Функціональні іконки-кнопки:aria-labelописує дію. - SC 1.4.3 Контрастність (Level AA): Звичайний текст - 4.5:1, великий - 3:1, UI-компоненти - 3:1.
- SC 2.1.1 Клавіатура (Level A): Весь функціонал доступний з клавіатури. Кожен
clickна ненативному елементі потребує відповідногоkeydownдляEnterі частоSpace. - SC 3.3.1 та 3.3.2 (Level A): Помилки описані текстом, не тільки кольором. Повідомлення програмно повʼязане з інпутом через
aria-describedby, інпут маєaria-invalid='true'. - SC 4.1.2 Імʼя, роль, значення (Level A): Для всіх UI-компонентів імʼя, роль і стан мають визначатись асистивними технологіями.
Частина V - Типові production-помилки і паттерн виправлення
- Кнопки з `<div>` і `<span>`:
<div onClick={handler}>- немає role, немає доступу з клавіатури. Використовуйте<button type='button'>для всіх інтерактивних елементів. - Кнопки-іконки без доступного імені: Додайте
aria-label='Закрити діалог'або прихований текст<span class='sr-only'>Закрити</span>. На SVG -aria-hidden='true'. - Поля форм без постійних лейблів: Завжди використовуйте
<label for>абоaria-label. Повідомлення про помилки:aria-describedby+aria-invalid='true'. - Інформація лише через колір: Доповнюйте колір текстовим лейблом, паттерном або іконкою з alt.
- Динамічний вміст без оголошення: Додайте
aria-live='polite'на контейнер із динамічними оновленнями. Контейнер має існувати в DOM до оновлення. - Нескінченне прокручування без виходу з клавіатури: Пагінація або кнопка 'Завантажити ще' в кінці батча.
Частина VI - Тестування
- Автоматизація: axe-core в CI:
@axe-core/playwright. Завершуйте збірку приcriticalабоseriousпорушеннях. - Тестування клавіатурою: Tab по всіх інтерактивних елементах. Кожен має бути досяжним, видимим при фокусуванні та керованим через
Enter/Space. - Тестування скринридером: На macOS: VoiceOver (
Command+F5) з Safari. На Windows: NVDA (безкоштовний) з Firefox або Chrome.
Частина VII - Доступні паттерни компонентів
- Модальні діалоги:
role='dialog',aria-modal='true',aria-labelledby. Фокус переміщується в діалог при відкритті, блокується всередині,Escapeзакриває, фокус повертається до тригера. HTML<dialog>обробляє більшість цього нативно. - Валідація форм:
aria-invalid='true'на інпуті,aria-describedbyвказує на повідомлення про помилку. Підсумкові помилки:aria-live='assertive'зведення вгорі форми. - ARIA live regions для toast-сповіщень:
<div role='status' aria-live='polite' aria-atomic='true'>оголошує зміни вмісту без переривання поточного читання. - Кастомні дропдауни vs нативний `<select>`: Нативний
<select>повністю доступний всюди. Якщо будуєте кастомний - використовуйте паттерн combobox з APG та@radix-ui/react-select. - Таблиці даних: Нативні
<table>,<th scope='col'>,<caption>. Уникайте CSS grid або divs для табличних даних.
Висновок
Борг із доступності в більшості production-застосунків виникає з одного джерела: інтерактивні компоненти на дженерик-елементах, лейбли що існують лише візуально, зміни стану невидимі для дерева доступності, і управління фокусом яке ніколи не розглядалось.
Практичний шлях: додайте axe-core в CI-пайплайн цього тижня. Додайте тестування клавіатурою до визначення готовності. Використовуйте нативні HTML-елементи там де використовуєте <div>. Використовуйте Radix UI або React Aria для складних віджетів.
Для аудиту доступності або доступної frontend-реалізації - послуга аудиту доступності | технічне SEO | обговорити проєкт.
Джерела
- W3C. 'Web Content Accessibility Guidelines (WCAG) 2.2': https://www.w3.org/TR/WCAG22/
- W3C. 'ARIA Authoring Practices Guide (APG)': https://www.w3.org/WAI/ARIA/apg/
- WebAIM. 'Screen Reader User Survey #10 (2024)': https://webaim.org/projects/screenreadersurvey10/
- WHO. 'Disability and Health': https://www.who.int/news-room/fact-sheets/detail/disability-and-health
- MDN Web Docs. 'Accessibility': https://developer.mozilla.org/en-US/docs/Web/Accessibility
- Deque. 'axe-core': https://github.com/dequelabs/axe-core
- Radix UI: https://www.radix-ui.com/
