Семантичний HTML і доступність (a11y)
Зміст
Доступність (accessibility, a11y) для AQA — це дві практичні речі одночасно: користувачі, які інакше не скористаються продуктом, і , які не залежать від верстки й тому переживають її рефакторинг. Обидві ростуть з одного кореня — правильної семантики розмітки — і ламаються теж разом: якщо автотест не знаходить кнопку за роллю, її не знайде й читач екрана.
Ця глава — повний виклад теми на сайті: семантика, дерево доступності, ARIA, критерії WCAG, ручна й автоматична перевірка, законодавча рамка. Матрицю браузерів, й адаптивну розкладку розбирає сусідня глава «Кросбраузерність і адаптивна верстка».
Семантичний HTML: правильний елемент для правильної роботи
Семантичний HTML (semantic HTML) — використання правильних елементів HTML за їхнім призначенням. Причина не в естетиці коду: браузер за це безкоштовно дає вбудовані «гачки» доступності, а порушена семантика — одне з головних місць, де доступність серйозно ламається.
Питання, яке виникає першим: навіщо, якщо CSS і JavaScript зроблять із будь-якого елемента що завгодно? Бо нативний <button> має не лише доречні стилі — він має вбудовану клавіатурну доступність: між кнопками переходять клавішею Tab і активують їх пробілом, Return або Enter. Те саме з посиланнями й полями форм: Tab перемикає фокус, Enter активує, стрілки гортають опції <select>. Це отримується безкоштовно, просто вибором доречного елемента.
<button>Відтворити відео</button>
<div tabindex="0" role="button">Відтворити відео</div>
Другий рядок — ціна «фальшивої кнопки»: tabindex="0" повертає можливість отримати фокус, зокрема через Tab, role="button" повідомляє користувачів читачів екрана, що з елементом можна взаємодіяти, і зверху потрібні власні обробники клавіатури. Тобто руками відтворюється те, що <button> дає з коробки. Візуальна схожість тут не рятує: віджет із <div> без ролі може бути несприйнятним або некерованим для людини з інвалідністю, бо допоміжна технологія не розпізнає його ролі. Побічний аргумент проти «зробимо потім»: послідовна семантика не довша в написанні й дає переваги поза доступністю — простішу підтримку й кращу індексацію пошуковими системами.
Структура і порядок у коді
Один із найкращих засобів доступності для користувача читача екрана — добра текстова структура: заголовки, абзаци, списки, а до них секційні елементи <nav>, <footer>, <article> як додаткові підказки для навігації. Без заголовків читач екрана не має дороговказів: зміст сторінки не витягується, і вся сторінка сприймається як один гігантський блок, що зачитується суцільно.
Третя частина структури — порядок у вихідному коді. CSS може переставити блоки візуально, але логічним порядок має бути в джерелі: саме в ньому вміст зачитають.
Пастка
Найдешевша перевірка структури не потребує жодного інструмента: спробуйте скласти зміст сторінки за самими лише заголовками. Не складається — читач екрана теж його не отримає.
Посилання проти кнопок
<a href="#"> або href="javascript:void(0)" з обробником onclick — класичний антипатерн. Ламається копіювання й перетягування посилань, відкриття в новій вкладці, закладки й поведінка, поки JavaScript ще вантажиться чи вимкнений, — і окремо: допоміжним технологіям передається хибна семантика. Рекомендація MDN однозначна: у таких випадках потрібен <button>.
Дзеркальна вимога: посилання з target="_blank" або посилання на файл має містити індикатор того, що станеться при активації — несподіване перемикання дезорієнтує людей зі слабким зором, користувачів читачів екрана й людей із когнітивними особливостями. І колір не може бути єдиною ознакою посилання: потрібні 4.5:1 до тла, щонайменше 3:1 між текстом посилання й навколишнім текстом і 3:1 між станами — звичайним, відвіданим, фокусом.
Дерево доступності: друге подання сторінки
Браузер перетворює розмітку на внутрішнє подання — DOM-дерево, — а на його основі будує окреме дерево доступності (accessibility tree). Саме ним платформні API доступності віддають сторінку допоміжним технологіям (assistive technology), зокрема читачам екрана.
Ключове слово тут — окреме: DOM і дерево доступності не те саме, тому перевіряти доступність за самою лише вкладкою Elements недостатньо. Для цього є панель доступності в інструментах розробника.
| Властивість вузла | На яке питання відповідає |
|---|---|
name (імʼя) | Як до цього звернутися? Посилання з текстом «Read more» має імʼя «Read more» |
description (опис) | Що додати понад імʼя? Наприклад, яку інформацію містить таблиця |
role (роль) | Що це за річ: кнопка, панель навігації, список |
state (стан) | Позначено чи ні, згорнуто чи розгорнуто |
Крім цього дерево зазвичай містить інформацію про можливі дії: за посиланням можна перейти, у текстове поле можна вводити.
Доступне імʼя (accessible name) — імʼя елемента інтерфейсу; така властивість є в кожного платформного API доступності. Значення може походити з видимого джерела (текст на кнопці) або невидимого (текстова альтернатива, що описує піктограму), і читач екрана зчитує його разом із роллю: для кнопки «OK» він може вимовити «push-button OK». Доступний опис (accessible description) — додаткова інформація понад імʼя, візуально вона може бути непомітною.
Ось тут доступність перестає бути «чужою темою». Локатор за роллю та доступним іменем спирається рівно на ці поля — і саме тому не залежить від верстки: перейменування CSS-класів чи перебудова дерева доступності не змінюють. Побічний ефект іще цінніший: такий локатор працює детектором дефектів семантики. Стратегії пошуку елемента як предмет розбирає глава «DOM, селектори та події», а API локаторів конкретного інструмента — розділ «Інструменти».
ARIA: коли мові розмітки бракує слів
WAI-ARIA — технічна специфікація, що дає каркас для опису семантики там, де мова розмітки її не має; орієнтована вона насамперед на розробників, які створюють власні віджети й компоненти. Цілі специфікації названі прямо: розширити доступну інформацію, яку може подати автор; покращити доступність динамічного контенту, згенерованого скриптами; забезпечити сумісність із допоміжними технологіями.
Семантика ARIA ділиться на дві частини: ролі (тип елемента інтерфейсу, атрибут role) та стани й властивості, які ця роль підтримує (атрибути aria-*). Різниця принципова: роль — це тип, вона не змінюється з часом чи діями користувача, а стани й властивості описують саме те, що змінюється під час взаємодії.
Якщо запамʼятати з розділу одне речення, то це:
ARIA доповнює нативну семантику мови, а не заміняє її.
Нормативне продовження: коли мова-хост дає можливість із рівноцінною доступністю, треба брати саме її, а ARIA застосовувати лише там, де мові бракує потрібної ролі, стану чи властивості. Причина — найкращий можливий запасний варіант для агентів без підтримки ARIA і збереження цілісності семантики самої мови. Практичний висновок для ревʼю: надлишкова ARIA — теж дефект, і правильна правка тут не «додати атрибут», а «замінити елемент».
Дві пастки, на яких ловляться найчастіше.
Перша: role не змінює DOM. Коли роль ARIA перекриває семантику мови-хоста, у DOM не відбувається жодних змін — лише в дереві доступності. Тонкість поруч: role перекриває неявну роль елемента, але для станів і властивостей пріоритет мають еквіваленти мови-хоста, щоб уникнути конфлікту; звідси класична помилка на кшталт <button role="presentation">. Сама роль presentation знімає з елемента власну семантику: для елемента з цією роллю, який не отримує фокус, користувацький агент не повинен виставляти API доступності його неявну нативну семантику, але вміст і нащадки без явної чи успадкованої ролі presentation лишаються доступними.
Друга: стани треба оновлювати. Зміна стану або властивості породжує сповіщення для допоміжних технологій, яке може повідомити користувача про зміну. Звідси типовий дефект динамічного віджета: візуальний стан змінився (клас, колір, стрілочка перевернулася), а aria-* — ні. Для користувача читача екрана не сталося нічого.
WCAG: чотири шари, три рівні
Тепер нормативна рамка, у якій усе це живе. WCAG (Web Content Accessibility Guidelines) має чотиришарову структуру, і для тестувальника важливо, який шар із неї перевіряється.
Принципів чотири, і найменш очевидний з них — надійність: контент має бути достатньо надійним, щоб його інтерпретувало широке коло користувацьких агентів, включно з допоміжними технологіями. Настанов тринадцять, вони задають каркас і самі по собі не перевірювані. Критерії успіху — навпаки: вони надаються саме для випадків, де потрібні вимоги й перевірка відповідності — у проєктній специфікації, закупівлях, регулюванні, договорах. Техніки інформативні: «достатня» техніка показує один зі способів виконати критерій, а не єдиний обовʼязковий. Звідси правило написання кейсів: кейс пишеться на критерій успіху, а «сторінка відповідає настанові 1.4» — неперевірюване формулювання за побудовою стандарту.
Рівнів відповідності три: A (найнижчий), AA і AAA (найвищий), і вони кумулятивні — AA означає виконання всіх критеріїв A і AA. Промисловим орієнтиром є AA, і підстава названа в самому стандарті: вимагати AAA як загальну політику для цілих сайтів не рекомендується, бо для частини контенту виконати всі критерії AAA неможливо. Ще одна деталь, про яку забувають у звітах: відповідність оголошується лише для цілих сторінок і не досягається, якщо частину сторінки виключено.
Ця глава спирається на версію 2.2. Вона зворотно сумісна з 2.1, вилучає критерій 4.1.1 Parsing і додає девʼять нових: 2.4.11 Focus Not Obscured (Minimum) (AA), 2.4.12 Focus Not Obscured (Enhanced) (AAA), 2.4.13 Focus Appearance (AAA), 2.5.7 Dragging Movements (AA), 2.5.8 Target Size (Minimum) (AA), 3.2.6 Consistent Help (A), 3.3.7 Redundant Entry (A), 3.3.8 Accessible Authentication (Minimum) (AA), 3.3.9 Accessible Authentication (Enhanced) (AAA). Робоча група радить брати за ціль саме 2.2, навіть якщо формальні зобовʼязання посилаються на попередні версії.
| Критерій | Рівень | Суть |
|---|---|---|
| 1.1.1 Non-text Content | A | Нетекстовий контент має текстову альтернативу з тим самим призначенням (з переліченими винятками) |
| 1.4.3 Contrast (Minimum) | AA | Контраст тексту — щонайменше 4.5:1 (з винятками) |
| 1.4.4 Resize Text | AA | Текст масштабується до 200% без втрати контенту чи функціональності |
| 2.1.1 Keyboard | A | Уся функціональність керована з клавіатури, без вимог до таймінгу натискань; виняток — дії, що залежать від траєкторії руху |
| 2.4.7 Focus Visible | AA | Індикатор фокуса видимий |
| 2.5.8 Target Size (Minimum) | AA (нове у 2.2) | Ціль для вказівних пристроїв — щонайменше 24×24 CSS-пікселі (з винятками) |
| 4.1.2 Name, Role, Value | A | Імʼя й роль програмно визначувані, стани програмно встановлювані, про зміни надходять сповіщення |
Останній рядок варто прочитати уважно: це нормативна основа і для семантичного HTML, і для ARIA, а стосується він усіх компонентів інтерфейсу — включно з елементами форм, посиланнями й компонентами, згенерованими скриптами. <div> з обробником кліку цього критерію не виконує.
Пара 2.1.1 і 2.4.7 — зазвичай найдешевша ручна перевірка доступності: пройти основний сценарій самою лише клавіатурою й подивитися, чи видно фокус на кожному кроці. Тобто «прибрали синю рамку, бо негарно» — не питання смаку, а порушення критерію рівня AA. Порядок обходу за Tab при цьому можна показати накладкою в інструментах розробника, і розбіжність між візуальним порядком і порядком у коді видно одразу.
Критерій 1.4.4 звʼязує цю главу з сусідньою: вимога масштабування тексту прямо повʼязана із забороною user-scalable=no — WCAG вимагає щонайменше дворазового масштабування, а найкращою практикою є пʼятиразове. Сам метатег viewport як механізм розбирає глава «Кросбраузерність і адаптивна верстка».
І межа відповідальності стандарту, корисна в суперечках «чому це не полагодили»: доступність залежить не лише від контенту, а й від доступних браузерів та інших користувацьких агентів, свою роль відіграють і інструменти авторингу. Частину проблем правками сторінки закрити не можна.
Автоматизація: axe і межа 57%
Практичне питання: скільки з цього закриває машина. axe — рушій тестування доступності для сайтів та інших інтерфейсів на HTML, зроблений так, щоб вбудовуватися в наявне тестове середовище й ганятися поруч зі звичайними функціональними тестами: локально, без підключення до стороннього сервера, з перевіркою порушень на кількох рівнях вкладених iframe і автодобором правил за контекстом перевірки.
Головне число теми — з документації рушія: автоматично знаходиться в середньому 57% проблем WCAG. Решту закриває людина, а сумнівні випадки рушій віддає окремо як «неповні» — на ручний перегляд. Тобто зелений прогін автоматичної перевірки не означає відповідності WCAG.
Результат axe.run — не «пройшло/не пройшло», а чотири окремі масиви.
| Масив | Що означає | Що з ним робити |
|---|---|---|
violations | Провалені перевірки | Заводити дефекти: рушій декларує нуль хибних спрацювань (крім власних вад) |
passes | Пройдені перевірки | Бачити, що вже закрито; корисно разом із ручним тестуванням |
incomplete | Рушій не зміг вирішити — «елементи на перегляд» | Дивитися руками: це і є пропущена робота |
inapplicable | Відповідних елементів на сторінці немає | Не читати як успіх: правило просто не запускалося |
Причини потрапляння в incomplete названі прямо: технічні обмеження самого правила або помилка JavaScript під час перевірки. Для звіту про дефект рушій дає майже все готове: impact (minor, moderate, serious, critical) як шкалу пріоритету, description і helpUrl як пояснення, а для кожного проблемного вузла — фрагмент HTML і CSS-селектор у полі target. Тонкість: правило складається з окремих перевірок, і для правила повідомляється найвищий рівень серйозності серед провалених.
Перед формулюванням «порушує WCAG» варто глянути на теги правила: кожне має тег версії й рівня (wcag2a, wcag2aa, wcag21aa, wcag22aa тощо) або мітку best-practice, якщо це не вимога стандарту, а також номер критерію — wcag111 означає критерій 1.1.1. Правила покривають WCAG 2.0/2.1/2.2 рівнів A, AA і AAA плюс окремий набір кращих практик (наприклад, наявність h1 на сторінці), тож частина спрацювань порушенням стандарту не є. Теги experimental, ACT, TT, section508 стоять лише в частини правил — набір до прогону обирають свідомо, а не запускають «усе підряд».
Дві структурні межі бʼють по найболючіше. Рушій аналізує лише відрендерений контент (включно з візуально прихованим) — це свідоме рішення заради меншої кількості хибних спрацювань. Тому типовий сценарій має пʼятий крок: завантажити сторінку → викликати axe.run → перевірити або зберегти звіт → повторити для невідрендереного контенту, попередньо зробивши його видимим. Практично: модалка, вкладка й акордеон — три окремі прогони, а не один. Друга межа — середовище: підтримка JSDOM обмежена, і правило color-contrast там не працює, тобто контраст без реального браузера не закрити.
Цінність автоматизації при цьому не в заміні людини: вона економить час, не потребує спеціальної експертизи й дозволяє спрямувати експерта туди, де він справді потрібен. Вбудувати перевірку можна на модульному, інтеграційному чи браузерному рівні — і чим раніше, тим дешевше. Інтеграцію axe у конкретний розбирає розділ «Інструменти».
Lighthouse: та сама родина правил у вигляді бала
Другий автоматичний аудитор — Lighthouse, відкритий інструмент оцінки якості вебсторінок. Доступність тут одна з категорій, а не весь інструмент: поруч живуть продуктивність, SEO та інші перевірки. Запускають його трьома способами — у Chrome DevTools (зокрема на сторінках за автентифікацією), із командного рядка або як Node-модуль; саме останній вбудовують у безперервну інтеграцію, а для захисту від існує окремий Lighthouse CI. Обидва не-браузерні сценарії вимагають установленого Chrome.
Найцікавіше — як рахується число. Оцінка доступності — зважене середнє всіх перевірок доступності, причому ваги взято з оцінок впливу на користувача від axe: під капотом та сама родина правил, тільки подана як бал. Правила підрахунку варто знати точно:
- Кожна перевірка — pass або fail, без часткового заліку. Якщо частина кнопок має доступні імена, а частина ні, сторінка отримує 0 за цю перевірку.
- Ручні перевірки й перевірки з низьким впливом в оцінку не входять — на число вони не впливають узагалі.
- Ваги різні, максимум у таблиці — 10: його мають, зокрема, доступні імена кнопок, підписи до полів форми й заборона
user-scalable="no". Достатній контраст тексту й фону до максимуму не належить — у нього 7.
Пастка
100 балів доступності ≠ доступний сайт, і це випливає з методики, а не з песимізму: ручні й low-impact перевірки в оцінку не входять за побудовою. Читати треба перелік провалених перевірок, а не бал; порівнювати бали двох сторінок «у лоб» теж не варто — оцінка зважена, і однакове число може означати різні набори проблем.
MDN згадує Lighthouse саме в переліку інструментів аудиту доступності — поруч із Firefox Accessibility Inspector і ANDI — і одразу називає межу: такі інструменти самі по собі проблем доступності не вирішують, потрібна комбінація інструментів, знань, досвіду й тестування з користувачами.
Читач екрана: чим і як перевіряють руками
Читач екрана (screen reader) — найпоширеніший вид допоміжних технологій і водночас найпоширеніший інструмент перевірки доступності. Це програма, що зачитує вміст сторінки чи інших застосунків і дозволяє користуватися компʼютером, не бачачи візуального вмісту. Працює вона не з HTML: браузер віддає їй дерево доступності — імена й описи елементів, їхню роль і стан.
MDN ділить читачі на три групи: платні комерційні — JAWS (Windows); безкоштовні — NVDA (Windows), ChromeVox (Chrome, Windows, macOS), Orca (Linux); вбудовані в операційну систему — VoiceOver (macOS та iOS), ChromeVox (Chromebook), TalkBack (Android). Ціна входу різна й це впливає на план: NVDA лише під Windows і встановлюється окремо, а VoiceOver іде безкоштовно з macOS, iPhone та iPad — на техніці Apple перевірка не потребує жодних установок. MDN радить тримати один-два читачі прямо на робочій машині й спробувати попрацювати ними на власних улюблених сайтах.
Базова механіка керування однакова: усе будується на клавіші-модифікаторі, яку тиснуть разом зі скороченнями, щоб команди читача не конфліктували з системними. У VoiceOver це CapsLock або Ctrl + Option, у NVDA — Insert (типово) або CapsLock.
Мінімальна методика — чотири проби:
- Заголовки. Чи знаходить їх читач і чи можна ними навігувати. Без семантичних заголовків він не отримує цієї інформації взагалі.
- Посилання поза контекстом. У списку посилань кожен текст має бути зрозумілим сам по собі; набір «click here» пробу провалює.
- Поля форми. Із
<label>читач озвучує підпис поля, без нього — «blank». - Таблиці. З правильними заголовками клітинка зачитується разом зі своєю колонкою й рядком; без них не звʼязується нічого.
Цей набір не дублює автоматику: axe перевірить наявність <label>, але не оцінить, чи має сенс те, що зачитано. І застереження для звіту: поведінка та керування в різних читачів відрізняються, тож «перевірено читачем екрана» без назви й версії — неповний звіт.
Читачі екрана — не вся картина допоміжних технологій. Є ще екранні збільшувачі (збільшення, корекція кольору, розмір курсора), програми розпізнавання мовлення (керування голосом і диктування), перемикачі (switch controls) для людей із дуже обмеженою рухливістю та альтернативні вказівні пристрої — трекболи, джойстики, тачпади.
Доступність як вимога закону
У частини команд доступність — не питання доброї волі. European Accessibility Act (EAA) — директива ЄС (офіційно — Директива (ЄС) 2019/882), мета якої прибрати барʼєри від різних національних правил і налагодити спільний ринок доступних продуктів і послуг; спирається вона на зобовʼязання з Конвенції ООН про права осіб з інвалідністю.
Ключова відмінність від сусідньої норми — обсяг. Директива 2016/2102 про доступність вебсайтів і мобільних застосунків стосується лише публічного сектору й спирається на WCAG 2.1; у реєстрі політик W3C обсяг EAA зазначено як публічний і приватний сектор, а версія WCAG — 2.2. Акт покриває не «все підряд», а перелік продуктів і послуг, визнаних найважливішими для людей з інвалідністю: компʼютери й операційні системи, смартфони, банківські послуги, електронні книги, електронну комерцію, телефонні послуги, банкомати й термінали продажу квитків, послуги пасажирських перевезень.
Пастка
Рядок про електронну комерцію — той, через який тема стосується звичайного вебпродукту. Якщо ви тестуєте інтернет-магазин, доступність перестає бути «доброю практикою» і стає предметом .
Дати в директиві стоять окремо: держави-члени мали ухвалити національні заходи до 28 червня 2022, а застосовувати їх — із 28 червня 2025. Поруч живуть застереження, без яких дата вводить в оману: окремі зобовʼязання держава-член могла відкласти щонайбільше до 28 червня 2027, для послуг на раніше законно вживаних продуктах передбачено перехідний період до 28 червня 2030, а старий вміст виведено з обсягу за датою публікації — зокрема архівні розділи сайту, які після 28 червня 2025 не оновлюють і не редагують.
Легко склеїти два різні акти й у стандартах. EN 301 549 (він включає WCAG 2.1 рівня AA дослівно, без змін, для вебвмісту, а для не-вебдокументів і софту — WCAG 2.0 AA в тлумаченні WCAG2ICT) — це поле Relevant Standard картки директиви 2016/2102; картка EAA такого поля не має взагалі й називає WCAG 2.2. Спільне в них те, що власних критеріїв європейські норми не вигадують, тож наявний чекліст WCAG лишається робочим інструментом — але версію й акт беруть ті, що справді стосуються продукту. І останнє: директива переноситься в національне законодавство, тому конкретні вимоги звіряють із законом ринку, на якому продається продукт.
Наша практика (не канон). «Дедлайн уже минув» — не привід закривати тему в плані аудиту: актуальна вимога до конкретного продукту залежить від його історії (коли продукт виведено на ринок, чи діє перехідний період), а не лише від календаря. Директива описує строки, а не тестову практику — цей висновок для планування наш.
Типові помилки
- Виглядає як «додамо
roleіaria-*— буде доступно», а насправді ARIA доповнює нативну семантику, а не заміняє її: якщо існує нативний елемент, правильна правка — замінити розмітку, а не додати атрибут. - Виглядає як «в Elements атрибут стоїть, значить працює», а насправді
roleDOM не змінює — змінюється лише дерево доступності, і дивитися треба в панель доступності. - Виглядає як «прогін axe зелений — сторінка відповідає AA», а насправді автоматика знаходить у середньому 57% проблем, сумнівне лежить в
incomplete, а відповідність оголошується для цілих сторінок. - Виглядає як «
inapplicable— це пройдені перевірки», а насправді правило просто не запускалося: відповідних елементів на сторінці не знайшлося. - Виглядає як «axe знайшов порушення WCAG», а насправді частина правил має тег
best-practiceі вимогою стандарту не є — рівень видно з тегів правила. - Виглядає як «100 балів у Lighthouse — доступний сайт», а насправді ручні й low-impact перевірки в оцінку не входять за побудовою методики.
- Виглядає як «віджет змінив стан», а насправді змінився лише CSS-клас: без оновлення
aria-*допоміжна технологія сповіщення не отримає. - Виглядає як «доступність — це для державних сайтів», а насправді це обсяг директиви 2016/2102; EAA охоплює й приватний сектор, зокрема електронну комерцію.
Підсумок
- Доступність вибудовується знизу вгору, і перший рівень найдешевший: нативний елемент дає клавіатуру, роль і структуру безкоштовно, а ARIA потрібна лише там, де мові розмітки бракує слів.
- DOM і дерево доступності — різні подання: допоміжна технологія й локатор за роллю бачать імʼя, опис, роль і стан, а не вашу верстку.
- Перевіряється лише критерій успіху, а не настанова; рівні кумулятивні, промисловий орієнтир — AA, і відповідність оголошується для цілої сторінки.
- Автоматика закриває, за даними axe, у середньому 57% і чесно позначає сумнівне; решта — клавіатурний прохід, видимість фокуса, структура заголовків, надлишкова ARIA, оновлення станів і осмисленість доступних імен.
- Для продуктів у периметрі EAA (зокрема електронної комерції) доступність є вимогою до продукту, а цільова версія стандарту в реєстрі політик W3C — WCAG 2.2.
Можливі питання
- «Чим
<div>з обробником кліку гірший за<button>?» Перевіряють, чи бачите ви за семантикою механізм. Сильна відповідь називає, що саме доводиться відтворювати руками — фокус, активацію з клавіатури, роль — і додає, що критерій 4.1.2 такий елемент не виконує. - «Що таке дерево доступності і чим воно відрізняється від DOM?» Дивляться, чи розумієте, що це друге подання, побудоване на основі DOM, і що звідси випливає: перевірка у вкладці Elements недостатня, а локатор за роллю стабільніший за CSS-селектор.
- «Коли застосовувати ARIA?» Очікують головне правило («доповнює, а не заміняє») і приклад надлишкової ARIA як дефекту, а не лише перелік атрибутів.
- «Як влаштований WCAG і що таке рівні A/AA/AAA?» Питання про нормативну грамотність: перевірюваними є лише критерії успіху, рівні кумулятивні, AAA не рекомендують як загальну політику, відповідність — для цілих сторінок.
- «Скільки проблем доступності можна знайти автоматично?» Слухають число й межу: у середньому 57%, сумнівне — в
incomplete, зелений прогін відповідності не означає. Слабка відповідь зупиняється на «прогнали axe». - «З чого почнете ручну перевірку доступності?» Міні-кейс, у якому важить порядок: клавіатурний прохід і видимість фокуса, потім структура заголовків, потім читач екрана з чотирма пробами — заголовки, посилання, підписи полів, таблиці.
Джерела
Семантичний HTML: правильний елемент для правильної роботи
- MDN — HTML: A good basis for accessibility — вбудована клавіатурна доступність нативних елементів, ціна фальшивої кнопки, структура й порядок у джерелі, псевдокнопки з посилань, контраст посилань.
- W3C — Accessible Rich Internet Applications (WAI-ARIA) 1.2 — чому візуальна схожість не замінює семантики: без розпізнаваної ролі віджет некерований для допоміжної технології.
Дерево доступності: друге подання сторінки
- MDN — Accessibility tree (glossary) — побудова дерева на основі DOM, чотири властивості вузла, можливі дії, незалежність локаторів за роллю від верстки.
- W3C — Accessible Rich Internet Applications (WAI-ARIA) 1.2 — доступне імʼя й опис, зчитування імені разом із роллю, API доступності як канал до допоміжних технологій.
ARIA: коли мові розмітки бракує слів
- W3C — Accessible Rich Internet Applications (WAI-ARIA) 1.2 — призначення й цілі специфікації, ролі проти станів і властивостей, правило доповнення нативної семантики,
roleне змінює DOM, пріоритет мови-хоста, рольpresentation, сповіщення про зміну стану. - MDN — HTML: A good basis for accessibility — мінімальна латка для фальшивої кнопки:
tabindex="0"плюсrole="button".
- W3C — Web Content Accessibility Guidelines (WCAG) 2.2 — чотири шари й неперевірюваність настанов, принципи, кумулятивні рівні A/AA/AAA, відповідність цілої сторінки, зміни у 2.2, критерії 1.1.1, 1.4.3, 1.4.4, 2.1.1, 2.4.7, 2.5.8, 4.1.2, межа відповідальності стандарту.
- MDN —
<meta name="viewport">(viewport meta element) — вимога дворазового масштабування й пʼятиразове як найкраща практика. - MDN — HTML: A good basis for accessibility — показ порядку табуляції накладкою в інструментах розробника.
- axe-core — API documentation — чотири масиви результату й причини
incomplete, поля порушення, найвищий рівень серед перевірок, теги правил, лише відрендерений контент і типовий сценарій із пʼятим кроком. - axe-core — README (репозиторій) — вбудовування в наявне тестове середовище, 57% проблем WCAG автоматично, нуль хибних спрацювань, обмеження JSDOM, кращі практики поверх WCAG, цінність автоматизації.
- W3C — Web Content Accessibility Guidelines (WCAG) 2.2 — нормативна межа: перевірювані лише критерії успіху.
Lighthouse: та сама родина правил у вигляді бала
- Chrome for Developers — Introduction to Lighthouse — доступність як одна з категорій, три способи запуску, аудит за автентифікацією, Node-варіант і Lighthouse CI, вимога встановленого Chrome.
- Chrome for Developers — Lighthouse accessibility score — зважене середнє з вагами axe, «pass або fail» без часткового заліку, невходження ручних і low-impact перевірок, ваги 10 і 7.
- MDN — Accessibility tooling and assistive technology — Lighthouse серед інструментів аудиту поруч із Firefox Accessibility Inspector і ANDI, і межа таких інструментів.
Читач екрана: чим і як перевіряють руками
- MDN — Accessibility tooling and assistive technology — читач екрана як найпоширеніша допоміжна технологія й інструмент перевірки, робота з дерева доступності, три групи читачів, ціна входу, клавіша-модифікатор, чотири базові проби, відмінності між читачами та інші види допоміжних технологій.
- European Commission — European Accessibility Act (EAA) — мета директиви, опора на Конвенцію ООН, перелік покритих продуктів і послуг, перенесення в національне законодавство.
- W3C WAI — Web Accessibility Laws & Policies: European Union — офіційний номер 2019/882, обсяг «публічний і приватний сектор» і WCAG 2.2 в картці EAA, обсяг і WCAG 2.1 у картці директиви 2016/2102, EN 301 549 як поле саме її картки.
- Directive (EU) 2019/882 — European Accessibility Act (офіційний текст, EUR-Lex) — дати ухвалення й застосування заходів, відступ до 2027 року, перехідний період до 2030-го, виведення старого вмісту з обсягу за датою публікації.
Чому клікабельний <div> вважають дефектом, якщо візуально він не відрізняється від кнопки?
Бо однаковий малюнок не дає однакової поведінки. Справжній <button> уже стоїть у черзі обходу за Tab і спрацьовує від пробілу чи Enter — це поведінка платформи, писати її не треба. Щоб повторити те саме на <div>, доводиться вручну повертати фокусованість через tabindex="0", оголошувати тип елемента через role="button" і дописувати власні обробники клавіш; кожен пропущений шматок відрізає того, хто не працює мишею. Доки роль не оголошено, допоміжна технологія її не розпізнає — і віджет виявляється для неї несприйнятним або некерованим. Формально це не «недоопрацювання», а невиконання критерію 4.1.2: імʼя й роль компонента мають бути програмно визначувані. Звідси висновок для ревʼю: правильна правка — замінити тег, а не докласти атрибутів.
Як оцінити структуру сторінки за пʼять хвилин без жодного інструмента?
Спробувати скласти з неї зміст, дивлячись лише на заголовки. Якщо зміст виходить осмисленим, читач екрана теж отримає дороговкази й зможе стрибати по розділах; якщо заголовків немає або вони розставлені «за розміром шрифту», уся сторінка перетвориться для нього на один суцільний потік тексту без орієнтирів. Друга половина перевірки — секційні елементи <nav>, <footer>, <article>: вони дають додаткові точки навігації понад заголовки. Третя, найменш очевидна річ — порядок у вихідному коді: CSS вільно переставляє блоки візуально, але зачитуватиметься саме джерело, тому логіка має бути закладена там. Тому «виглядає правильно» ще нічого не доводить, поки не звірено порядок у коді.
Коли на кнопку треба брати <button>, а коли <a>?
Проста межа: посилання веде кудись, кнопка щось робить. Тому конструкції на кшталт <a href="#"> чи href="javascript:void(0)" з обробником кліку — класичний антипатерн: разом із ними ламається все, заради чого посилання й існує, — копіювання адреси, перетягування, відкриття в новій вкладці, закладки, робота до завантаження скриптів. Плюс окремо: допоміжній технології передається неправдива семантика, елемент оголошується як перехід, хоча нікуди не веде. Дзеркальна вимога стосується справжніх посилань: якщо клік відкриє нову вкладку або потягне файл, це має бути видно з тексту чи індикатора — несподівана зміна контексту збиває з пантелику одразу кілька груп: людей зі слабким зором, тих, хто працює читачем екрана, і людей із когнітивними особливостями. І сам факт «це посилання» не можна передавати самим лише кольором: до тла потрібні 4.5:1, до сусіднього тексту щонайменше 3:1 і стільки ж між станами — звичайним, відвіданим і фокусом.
Що таке дерево доступності і чому перевіряти доступність у вкладці Elements недостатньо?
Це друге подання тієї самої сторінки. Браузер спершу будує DOM, а на його основі — окреме дерево доступності, і саме його платформні API віддають допоміжним технологіям. Ключове слово «окреме»: збіг структур не гарантований, тому перевірка доступності поглядом у вкладку Elements неповна за побудовою — дивитися треба в панель доступності інструментів розробника. У вузлі цього дерева чотири головні властивості: імʼя (як до елемента звернутися), опис (що додати понад імʼя), роль (кнопка це, навігація чи список) і стан (позначено чи ні, згорнуто чи розгорнуто). Плюс зазвичай перелік доступних дій: за посиланням можна перейти, у поле можна вводити. Практичний наслідок: усе, чого немає в цьому дереві, для користувача читача екрана не існує, навіть якщо в розмітці воно виглядає бездоганно.
Що таке доступне імʼя і чим воно відрізняється від доступного опису?
Доступне імʼя (accessible name) — це те, як елемент називається для API доступності; така властивість є в кожної платформи. Джерело може бути видимим (напис на кнопці) або невидимим (текстова альтернатива до піктограми) — важливий результат, а не спосіб. Читач екрана вимовляє імʼя разом із роллю: для кнопки «OK» це прозвучить приблизно як «push-button OK», тобто користувач одразу чує і що це, і як воно зветься. Доступний опис (accessible description) — додатковий шар інформації понад імʼя, візуально він може бути взагалі непомітним: наприклад, пояснення, які дані містить таблиця. Для тестувальника різниця не академічна: працюють з іменем, тож піктограма без текстової альтернативи одночасно недоступна для людини й непошуковна для автотеста.
Чому локатор за роллю та доступним іменем вважають стабільнішим за CSS-селектор?
Тому що він спирається на інші поля. CSS-селектор читає верстку — класи, вкладеність, атрибути оформлення, — а вони змінюються від будь-якого рефакторингу стилів чи перебудови . Локатор за роллю бере роль і доступне імʼя з дерева доступності, а їх перейменування класу не зачіпає: доки кнопка лишається кнопкою з тим самим написом, локатор живий. Бонус ще цікавіший за стабільність: такий локатор мимохідь працює детектором дефектів семантики. Якщо пошук кнопки за роллю не знаходить нічого, це майже завжди означає, що кнопки в дереві доступності немає, — тобто ви натрапили не на проблему тесту, а на реальний барʼєр, який так само зустріне користувач читача екрана. Тому «локатор не працює, обійду через CSS» — це рішення, яке ховає баг замість того, щоб його завести.
Що таке WAI-ARIA і для кого вона взагалі призначена?
Це специфікація W3C, яка дає словник для опису семантики там, де мові розмітки бракує потрібних слів. Адресат у неї доволі вузький — розробники власних віджетів і компонентів, тобто тих речей, під які готового елемента в HTML просто немає. Цілі названі в самому документі: розширити обсяг доступної інформації, яку може передати автор; підтягнути доступність динамічного вмісту, згенерованого скриптами; забезпечити сумісність із допоміжними технологіями. Словник ділиться надвоє: ролі, які задає атрибут role, і підпорядкований конкретній ролі набір станів та властивостей із префіксом aria-. Типова помилка сприйняття — вважати ARIA «шаром доступності», який накладають зверху на будь-яку розмітку; насправді вона потрібна саме там, де нативної відповідності немає.
Яке головне правило застосування ARIA?
Одне речення: ARIA доповнює нативну семантику мови розмітки, а не заміняє її. Нормативне продовження звучить так: якщо мова-хост дає елемент із рівноцінною доступністю, брати треба саме його, а ARIA лишати на випадки, де ролі, стану чи властивості в мові немає. Причин дві. Перша — запасний варіант: агент, який ARIA не підтримує, з нативного елемента все одно отримає правильну семантику, а з <div> з атрибутами — нічого. Друга — цілісність семантики самої мови, яку розхитує довільне перекриття ролей. Для тестувальника з цього випливає контрінтуїтивний висновок: надлишкова ARIA — теж дефект, і правильна правка на нього не «додати атрибут», а «замінити елемент». Тому <button role="button"> у ревʼю — не нейтральна надмірність, а рівно той випадок, коли атрибут дублює те, що елемент уже має від народження.
Чим роль відрізняється від станів і властивостей?
Роль — це тип елемента, і вона незмінна: кнопка не перестає бути кнопкою від того, що користувач її натиснув. Стани й властивості (aria-*) описують якраз те, що змінюється під час взаємодії: розгорнуто чи згорнуто, позначено чи ні, вимкнено чи активно. З цієї різниці випливає найпоширеніший дефект динамічних віджетів: при зміні стану інтерфейс оновлює клас, колір і напрямок стрілочки, а відповідний aria-* лишає як був. Візуально все правильно, а для допоміжної технології не сталося нічого — саме зміна стану чи властивості породжує сповіщення, яке має дійти до користувача. У тесті це ловиться дешево: після дії перевіряється не тільки видимість панелі, а й стан елемента керування, який її розгортає.
Атрибут role стоїть у розмітці, а в DOM нічого не змінилося. Це баг?
Ні, це задокументована поведінка: перекриття нативної семантики роллю з ARIA у самому DOM не відображається взагалі — результат видно тільки в дереві доступності. Тому «в Elements атрибут на місці, значить працює» — хибний висновок, підтверджувати треба в панелі доступності. Поруч є тонкість, на якій ловляться: role перекриває неявну роль елемента, а от для станів і властивостей пріоритет мають еквіваленти самої мови — так уникають конфлікту двох джерел істини. Звідси й народжується класична помилка <button role="presentation">: роль presentation гасить власну семантику елемента, і якщо такий елемент фокус не отримує, віддавати його неявну нативну семантику в API доступності користувацькому агенту заборонено. При цьому вміст і нащадки, що не мають явної чи успадкованої ролі presentation, лишаються доступними — тобто результат виходить не «елемент зник», а плутана напівсемантика.
Як влаштований WCAG і що в ньому таке рівні A, AA і AAA?
Стандарт має чотири шари, і для тестувальника критично, з якого шару взято формулювання. Зверху чотири принципи — сприйнятність, керованість, зрозумілість, надійність; під ними тринадцять настанов, які задають цілі й самі по собі не перевірювані; далі критерії успіху — єдине, що перевіряється і на що можна писати вимогу в договір чи закупівлю; знизу техніки, статус яких суто інформативний — позначена як «достатня» техніка є одним із робочих варіантів реалізації, а не єдиним дозволеним. Тому кейс завжди привʼязують до конкретного критерію: фраза «сторінка відповідає настанові 1.4» неперевірювана не через недбалість автора, а через саму будову стандарту. Рівнів три, і вони кумулятивні: AA означає виконання і всіх критеріїв A, і всіх критеріїв AA. Промисловим орієнтиром зупинилися на AA, і підстава лежить у самому стандарті — ставити AAA загальною політикою цілого сайту не радять, бо існує контент, для якого весь набір критеріїв цього рівня недосяжний у принципі. І деталь, яку регулярно забувають у звітах: відповідність оголошують для цілих сторінок, тож формулювання «сторінка відповідає AA, крім віджета оплати» невалідне.
Що змінилося у WCAG 2.2 і чому радять брати за ціль саме її?
Версія зворотно сумісна з 2.1: усе, що виконувало 2.1, лишається виконаним. Одна річ із неї вилучена — критерій 4.1.1 Parsing, і додано девʼять нових: 2.4.11 Focus Not Obscured (Minimum) (AA), 2.4.12 Focus Not Obscured (Enhanced) (AAA), 2.4.13 Focus Appearance (AAA), 2.5.7 Dragging Movements (AA), 2.5.8 Target Size (Minimum) (AA), 3.2.6 Consistent Help (A), 3.3.7 Redundant Entry (A), 3.3.8 Accessible Authentication (Minimum) (AA), 3.3.9 Accessible Authentication (Enhanced) (AAA). Робоча група радить цілитися в 2.2 навіть тим командам, чиї формальні зобовʼязання посилаються на старіші версії, — саме через зворотну сумісність нічого не втрачається. Практичний зріз для мобільного вебу дає 2.5.8: ціль для вказівних пристроїв має бути не меншою за 24×24 CSS-пікселі.
Що саме вимагає критерій 4.1.2 Name, Role, Value?
Щоб для компонента інтерфейсу імʼя й роль були програмно визначувані, стани й значення — програмно встановлювані, а про їхні зміни надходили сповіщення допоміжним технологіям. Читати цей критерій треба уважно в частині обсягу: він стосується всіх компонентів — і елементів форм, і посилань, і того, що згенерував скрипт. Тобто <div> з обробником кліку його не виконує не тому, що «так не прийнято», а тому, що в нього немає програмно визначуваної ролі. Це нормативна основа одразу для двох частин теми: і для вимоги брати семантичний елемент, і для вимоги тримати aria-* синхронними з видимим станом. У баг-репорті посилання саме на 4.1.2 переводить розмову з «мені здається, це незручно» в площину невиконаної вимоги рівня A.
Дизайнер попросив прибрати рамку фокуса, бо вона псує вигляд. Чим це аргументувати?
Тим, що видимий індикатор фокуса — це критерій 2.4.7 рівня AA, тобто не питання смаку, а невиконана вимога. Разом із ним у парі йде 2.1.1: керувати всією функціональністю можна з клавіатури, причому швидкість чи ритм натискань ролі не грають; єдиний виняток стандарт лишає для дій, суть яких завʼязана на траєкторію руху. Ця пара дає найдешевшу ручну перевірку доступності взагалі: пройти основний сценарій самою лише клавіатурою й на кожному кроці бачити, де зараз фокус. Компроміс тут існує й він відомий: стиль індикатора можна змінити, прибрати його не можна. Заодно на цьому ж проході видно другу проблему — розбіжність порядку табуляції з візуальним порядком; її показує накладка порядку обходу в інструментах розробника, і причина зазвичай у тому, що CSS переставив блоки, а джерело лишилося як було.
Скільки проблем доступності реально знаходить автоматика?
Документація axe називає число прямо: у середньому 57% проблем WCAG. Отже, майже половина лишається людині, і сама формула «зелений прогін = відповідність AA» неправильна за побудовою. Причина не в якості рушія, а в природі критеріїв: машина перевіряє наявність <label>, але не питання, чи має цей підпис сенс; вона бачить порожній alt, але не оцінює, чи описує альтернативний текст те саме, що зображення. Ще частину випадків axe не береться вирішувати сам і чесно віддає окремим масивом incomplete — «подивіться руками»; туди елемент потрапляє через технічні обмеження правила або через помилку JavaScript під час перевірки. Сильна відповідь на це питання завершується планом, а не числом: автоматика ставиться в якнайраніше, а звільнений час іде на клавіатурний прохід, структуру заголовків і перевірку осмисленості доступних імен.
Що означають чотири масиви результату axe.run і як їх читати?
Результат — не вердикт «пройшло чи ні», а чотири окремі списки. violations — провалені перевірки, з них заводять дефекти: рушій декларує нуль хибних спрацювань, тож знахідка звідти майже завжди реальна. passes — те, що вже закрито, корисно як картина . incomplete — випадки, які рушій не вирішив; це і є список ручної роботи, а не «майже пройдено». inapplicable — правило не запускалося, бо відповідних елементів на сторінці не знайшлося; читати це як успіх — типова помилка звіту. Для тікета в кожному порушенні вже є все потрібне: impact (minor, moderate, serious, critical) як шкала пріоритету, description і helpUrl як пояснення, а для кожного вузла — фрагмент розмітки й CSS-селектор у полі target. Тонкість для точності звіту: правило складається з кількох перевірок, і назовні йде найвищий рівень серйозності серед провалених — тому critical у рядку не завжди означає, що критичні всі перевірки правила.
Чому axe доводиться запускати кілька разів на одній сторінці?
Тому що рушій аналізує лише відрендерений контент — те, що є в дереві, включно з візуально прихованим. Це свідомий компроміс заради меншої кількості хибних спрацювань, але наслідок у нього прямий: модалка, яку ще не відкрили, вкладка, на яку не перемкнулися, і згорнутий акордеон для перевірки не існують. Через це до звичної послідовності «відкрити сторінку → запустити axe.run → заасертити чи зберегти звіт» додається ще один крок: показати те, що досі не рендерилося, і прогнати перевірку повторно. Практично це означає, що складна сторінка з трьома прихованими станами — це три прогони, а не один, і саме цей крок найлегше пропустити команді, яка вважає перевірку вже налаштованою. Друга структурна межа стосується середовища: у JSDOM рушій підтримано лише частково, і правило color-contrast там не працює взагалі — отже, без справжнього браузера контраст на модульному рівні не перевіриш.
Чому 100 балів доступності в Lighthouse не означають доступний сайт?
Бо так влаштована сама методика підрахунку. Бал — це зважене середнє перевірок доступності, а вагу кожної взято зі шкали впливу на користувача, яку веде axe; тобто під капотом працює та сама родина правил, лише результат подано числом. Звідси три наслідки. Перший: цілий пласт перевірок — ручні та ті, що мають низький вплив, — на число не впливає взагалі. Другий: часткового заліку немає, перевірка або зарахована, або ні, тож девʼять кнопок з доступними іменами й одна без дають рівно нуль. Третій: ваги нерівні — верхнє значення шкали дорівнює 10 і належить, зокрема, іменам кнопок, підписам полів форми та забороні user-scalable="no", тоді як достатній контраст тексту й тла оцінено в 7. Тому дивитися треба на список провалених перевірок, а не на цифру; і зіставляти бали двох сторінок навпростець теж марно — за однаковим числом легко ховаються різні набори проблем.
З чого почнете ручну перевірку доступності й що дасть читач екрана?
Порядок економить час, тому починають із найдешевшого: пройти основний сценарій самою клавіатурою й переконатися, що фокус видно на кожному кроці й не застрягає. Далі — структура заголовків, бо без неї не працює навігація читача екрана. І лише третім кроком сам читач із чотирма пробами: чи знаходить він заголовки й чи можна ними стрибати; чи зрозумілі тексти посилань, витягнуті зі списку поза контекстом (набір «click here» пробу провалює); чи озвучується підпис поля форми — з <label> він читається, без нього прозвучить «blank»; чи звʼязується клітинка таблиці зі своєю колонкою та рядком. Цей набір не дублює автоматику: axe скаже, що <label> існує, але не оцінить, чи має він сенс. І важлива дрібниця для звіту: керування й озвучення в різних читачів свої, тому запис «перевірено читачем екрана» без конкретного продукту та його версії неповний; на macOS та iOS під рукою безкоштовний VoiceOver, під Windows окремо ставиться безкоштовна NVDA, а з платних комерційних MDN називає JAWS.
Кого стосується European Accessibility Act і чим він відрізняється від директиви про сайти публічного сектору?
Насамперед обсягом. Норма 2016/2102 написана під сайти й мобільні застосунки органів влади — її периметр обмежений публічним сектором, а цільовою версією там названо WCAG 2.1; саме в її картці в реєстрі політик W3C стоїть стандарт EN 301 549. EAA — це Директива (ЄС) 2019/882, і в тому самому реєстрі її периметр охоплює вже і публічний, і приватний сектор, а версією названо WCAG 2.2. Покриває вона не все підряд, а список того, що визнали найважливішим для людей з інвалідністю: серед пунктів — банківські послуги, електронна комерція, електронні книги, смартфони, компʼютери разом з операційними системами, телефонний звʼязок, пасажирські перевезення, банкомати й термінали продажу квитків. Заради рядка про електронну комерцію тема й заходить у звичайний продуктовий : для інтернет-магазину доступність перестає бути доброю практикою й стає предметом . Дати без застережень вводять в оману: національні заходи мали бути ухвалені до 28 червня 2022 і застосовуються з 28 червня 2025, проте для окремих зобовʼязань допускалася відстрочка — крайня межа 28 червня 2027, послуги на продуктах, які законно використовувалися раніше, отримали перехідний період аж до 28 червня 2030, а вміст, опублікований до дати застосування й відтоді не редагований, із периметра виведений. І позаяк директива переноситься в національне законодавство, конкретні вимоги звіряють із законом того ринку, де продукт продається.
Три ситуації, у яких доступність перестає бути «темою для окремого спринта»: як відповісти на питання менеджера про зелений звіт, як зловити найпоширеніший дефект динамічного віджета звичайним автотестом і як пройти реліз руками за двадцять хвилин.
Кейс 1. «Lighthouse показує 100, axe зелений — можна писати, що ми відповідаємо AA?»
Питання прилітає в чат перед релізом, і відповідати на нього «ні, бо доступність складна» — програшна позиція. Сильна відповідь показує, що саме виміряли обидва інструменти й що лишилося за кадром.
| Що зроблено | Що це справді доводить | Чого воно не доводить |
|---|---|---|
Прогін axe на головній сторінці, violations порожній | Правила, які машина вміє вирішувати, на відрендереному вмісті не спрацювали | Що модалка, згорнутий акордеон і неактивна вкладка перевірені: їх у дереві не було |
incomplete ніхто не відкривав | Нічого | Саме тут лежить робота, яку рушій свідомо віддав людині |
Багато рядків в inapplicable | На сторінці немає елементів, до яких правило застосовне | Що ці перевірки пройдені: правило просто не запускалося |
| Оцінка доступності Lighthouse = 100 | Усі перевірки, що входять в оцінку, зараховані | Що проблем немає: ручні й low-impact перевірки в бал не входять узагалі |
| Прогін у модульних тестах на JSDOM | Частину структурних правил закрито | Контраст: color-contrast у JSDOM не працює |
Що з цього складається у відповідь.
- Число «57%» — не песимізм, а параметр планування. Автоматика знаходить у середньому 57% проблем WCAG, тож зелений звіт закриває приблизно половину роботи. Друга половина — клавіатурний прохід, структура заголовків, осмисленість доступних імен і синхронність станів.
- Формально «відповідає AA» взагалі не можна сказати про частину сторінки. Відповідність оголошується для цілих сторінок і не досягається, якщо якийсь фрагмент виведено за дужки — тож застереження «крім віджета оплати» робить твердження невалідним, а не обережним.
- Перед словом «порушення» варто глянути на теги правила. Частина правил має мітку
best-practice— це рекомендація, а не вимога стандарту (класичний приклад — наявністьh1на сторінці). Рівень і версію видно з тегівwcag2a,wcag21aa,wcag22aa, а номер критерію — з тега на кшталтwcag111. - Три прогони замість одного. Складна сторінка перевіряється покроково: базовий стан, потім кожен прихований стан після того, як його зробили видимим. Це і є той пʼятий крок сценарію, який у більшості команд просто не написали.
Робоче формулювання для звіту виглядає так — і воно чесніше за «ми відповідаємо AA»:
Автоматична перевірка: axe (3 прогони: базовий стан, модалка оформлення, вкладка «Доставка»).
violations: 0. incomplete: 7 (розібрано вручну, 2 перетворені на дефекти).
Ручна перевірка: клавіатурний прохід основного сценарію + VoiceOver (macOS 14).
Не перевірено: контраст у темній темі, таблиця історії замовлень.
Кейс 2. Акордеон «розгорнувся», а для читача екрана нічого не сталося
Найчастіший дефект динамічного віджета виглядає так: користувач тисне заголовок секції, стрілочка перевертається, панель зʼїжджає вниз — усе працює. У розмітці при цьому змінюється лише клас, а aria-expanded як був false, так і лишився. Візуально бездоганно, а для допоміжної технології стан не змінився: саме зміна стану породжує сповіщення, якого тут не буде.
Ловиться це тестом, який виглядає як звичайний функціональний, — просто стоїть не на класі:
import { test, expect } from '@playwright/test';
test('акордеон повідомляє про свій стан, а не тільки перемальовується', async ({ page }) => {
await page.goto('/faq');
// локатор за роллю та іменем: він же перевіряє, що елемент узагалі є кнопкою
const toggle = page.getByRole('button', { name: 'Умови доставки' });
await expect(toggle).toHaveAttribute('aria-expanded', 'false');
await toggle.click();
// панель видно — це бачить і зрячий користувач
await expect(page.getByRole('region', { name: 'Умови доставки' })).toBeVisible();
// а це бачить читач екрана: без асерту дефект проходить непоміченим
await expect(toggle).toHaveAttribute('aria-expanded', 'true');
});
Той самий тест мимохідь ловить і сусідній клас дефектів. Якщо getByRole('button', ...) не знаходить нічого, а на сторінці візуально кнопка є — це не проблема , а знахідка: у дереві доступності кнопки немає, тобто на її місці стоїть <div> без ролі. Спокуса «обійду через CSS-селектор» тут дорого коштує: тест позеленіє, а барʼєр залишиться і в продукті.
// так робити не треба: обхід ховає реальний дефект семантики
// await page.locator('.faq__item-header').click();
Автоматичний рушій у той самий сценарій вбудовується як окремий крок — і саме тут видно, чому прогонів має бути кілька:
import AxeBuilder from '@axe-core/playwright';
test('перевірка доступності до й після розкриття секції', async ({ page }) => {
await page.goto('/faq');
const closed = await new AxeBuilder({ page }).analyze();
expect(closed.violations).toEqual([]);
await page.getByRole('button', { name: 'Умови доставки' }).click();
// до кліку вмісту панелі в дереві не було — перший прогін його не бачив
const opened = await new AxeBuilder({ page }).analyze();
expect(opened.violations).toEqual([]);
// incomplete не валить тест, але має потрапити в звіт: це ручна робота
console.log('на ручний перегляд:', opened.incomplete.length);
});
Що дивитися і чому:
- Асерт на
aria-*— це асерт на контракт із допоміжною технологією, а не «зайва перевірка». Клас, колір і висота панелі описують оформлення; стан елемента керування описує сенс. - Роль плюс доступне імʼя — найдешевший детектор семантики. Він не залежить від верстки, тому переживає рефакторинг стилів; і він падає рівно тоді, коли ламається те, що потрібне користувачеві.
violations— у тікет,incomplete— у звіт. Другий масив не має валити прогін, але й губитися не має: це елементи, які рушій свідомо передав людині.- Порядок прогонів визначає . Кожен прихований стан — окремий прогін після того, як його зроблено видимим; інакше перевіряється лише те, що вже було в дереві.
Кейс 3. Двадцять хвилин перед релізом: ручний прохід і як його записати
Автоматика вже відпрацювала, часу на повний аудит немає. Порядок кроків тут не косметичний — він поставлений від найдешевшого до найдорожчого, щоб дорогі кроки не витрачалися на те, що видно й так.
| Крок | Що робити | Ознака проблеми | Куди веде |
|---|---|---|---|
| 1. Клавіатура | Пройти основний сценарій самим Tab, Shift+Tab, Enter, пробілом | Фокус зникає, застрягає в пастці або елемент не активується | 2.1.1 (A) |
| 2. Видимість фокуса | На кожному кроці дивитися, де зараз фокус | Індикатор прибраний «бо негарно» | 2.4.7 (AA) |
| 3. Порядок обходу | Увімкнути накладку порядку табуляції в інструментах розробника | Стрибки проти візуального порядку | порядок у вихідному коді |
| 4. Заголовки | Скласти зміст сторінки з самих заголовків | Зміст не складається або складається з випадкових рядків | структура розмітки |
| 5. Читач екрана | Чотири проби: заголовки, посилання поза контекстом, підписи полів, таблиці | «blank» замість підпису, список із десяти «Детальніше» | 4.1.2 (A) — доступне імʼя |
Кілька зауваг, які роблять цей прохід корисним, а не ритуальним.
- Крок 5 не дублює axe. Рушій підтвердить, що
<label>існує; чи має цей підпис сенс і чи зрозуміле посилання, вирване з контексту, — оцінює тільки людина. - «Прибрали синю рамку» — не питання смаку. Стиль індикатора змінювати можна, прибирати не можна: це критерій рівня AA, і в тікеті це формулюється саме так.
- Читач екрана — це назва плюс версія. Поведінка й керування в різних читачів відрізняються, тому «перевірено читачем екрана» без уточнення — неповний запис. Клавіша-модифікатор, з якою тиснуть усі скорочення, теж різна: NVDA типово чекає на
InsertіCapsLock, VoiceOver працює зCapsLockабо сполученнямCtrl + Option. - Один-два читачі варто тримати прямо на робочій машині. На техніці Apple перевірка не коштує нічого — VoiceOver уже встановлений; під Windows NVDA ставиться окремо.
Готовий каркас тікета, у який лишається підставити конкретику:
Заголовок: [a11y][AA] Кнопка «Оформити» недосяжна з клавіатури в модалці оплати
Критерій: WCAG 2.2 — 2.1.1 Keyboard (рівень A), 4.1.2 Name, Role, Value (рівень A)
Джерело: ручна перевірка (клавіатурний прохід) + axe, impact: serious
Елемент: <div class="checkout__submit"> (target зі звіту axe)
Кроки:
1. Відкрити кошик, натиснути «Оформити замовлення».
2. У модалці переходити далі клавішею Tab.
Фактично: фокус проходить повз кнопку підтвердження; активувати її з клавіатури неможливо.
Очікувано: елемент є нативною кнопкою, отримує фокус за Tab і спрацьовує від Enter та пробілу.
Примітка: правка — заміна елемента на <button>, а не додавання tabindex і role.
Останній рядок у тікеті найважливіший: без нього велика ймовірність отримати латку з tabindex="0" і role="button" замість нативного елемента — тобто менший, але той самий дефект.
Семантика розмітки
- Можу назвати повну ціну фальшивої кнопки:
tabindex="0"плюсrole="button"плюс власні обробники клавіатури — тобто рівно те, що нативний<button>дає без жодного рядка коду. - Знаю різницю «посилання веде — кнопка робить» і можу сказати, що саме ламає
<a href="#">з обробником кліку: закладки, нову вкладку, копіювання адреси, роботу до завантаження скриптів і саму семантику для допоміжної технології. - Можу провести найдешевшу перевірку структури без інструментів — скласти зміст сторінки за самими заголовками — і пояснити, чому важить порядок у вихідному коді, а не візуальний.
Дерево доступності
- Розумію, що це окреме подання, побудоване на основі DOM, і можу назвати чотири властивості вузла — імʼя, опис, роль, стан; звідси й висновок, що вкладки Elements для перевірки не досить.
- Знаю різницю доступного імені vs доступного опису (перше зачитується разом із роллю, друге додає деталі й може бути невидимим) і чому за роллю переживає рефакторинг класів, а його провал зазвичай означає дефект семантики.
ARIA
- Знаю головне правило напамʼять — ARIA нічого не заміняє, вона лише доповнює нативну семантику — і розумію, чому надлишкова ARIA теж дефект, а правильна правка на нього «замінити елемент», а не «додати атрибут».
- Знаю різницю ролі vs станів і властивостей (роль — незмінний тип,
aria-*описують мінливе) і можу назвати типовий дефект віджета: клас і стрілочка перемкнулися, стан для допоміжної технології — ні. - Памʼятаю, що
roleDOM не змінює: результат видно тільки в дереві доступності; для станів пріоритет за еквівалентами мови-хоста, звідси помилка на кшталт<button role="presentation">.
WCAG
- Знаю чотири шари й можу сказати, який із них перевіряється: критерій успіху так, настанова ні, техніка інформативна.
- Розумію, що рівні кумулятивні, орієнтир індустрії — AA, AAA не радять як загальну політику, а сама відповідність оголошується для цілої сторінки.
- Можу назвати зміни у версії 2.2: вилучений
4.1.1 Parsingплюс девʼять нових критеріїв, серед них2.5.8 Target Size (Minimum)(24×24 CSS-пікселі) і2.5.7 Dragging Movements. - Знаю зміст і обсяг
4.1.2 Name, Role, Value(усі компоненти, включно зі згенерованими скриптами) і можу аргументувати прибраний індикатор фокуса як порушення2.4.7рівня AA.
Автоматика: axe і Lighthouse
- Памʼятаю число й межу: у середньому 57% проблем WCAG знаходиться автоматично, тож зелений прогін відповідності не доводить; а перед словами «порушує WCAG» дивлюся на теги правила —
wcag2a/wcag21aa/wcag22aaпротиbest-practice. - Читаю чотири масиви без помилок:
violations— у дефекти,passes— картина ,incomplete— список ручної роботи,inapplicable— правило не запускалося (це не успіх). - Знаю, що перевіряється лише відрендерений вміст, тому модалка, вкладка й акордеон вимагають окремих прогонів; і що в JSDOM правило
color-contrastне працює. - Розумію, звідки береться бал Lighthouse — зважене середнє з вагами axe — і три наслідки: pass/fail без часткового заліку, ручні й low-impact перевірки поза оцінкою, різні ваги (10 у підписів полів, 7 у контрасту).
Ручна перевірка й читач екрана
- Знаю, у якому порядку йти руками: спершу клавіатура разом із видимістю фокуса, далі заголовки як каркас навігації, і аж тоді читач екрана.
- Можу перелічити чотири базові проби читачем — заголовки, посилання поза контекстом, підписи полів (без
<label>буде «blank»), звʼязок клітинок таблиці — і пояснити, чому вони не дублюють axe; орієнтуюся в ціні входу (VoiceOver уже є в macOS та iOS, NVDA ставиться окремо під Windows, JAWS комерційний) і памʼятаю, що звіт без назви й версії читача неповний.
Законодавча рамка
- Знаю різницю обсягів: директива 2016/2102 — публічний сектор і WCAG 2.1, EAA (Директива (ЄС) 2019/882) — публічний і приватний сектор, у реєстрі політик W3C з версією 2.2.
- Памʼятаю, через який рядок EAA тема стосується звичайного продукту (електронна комерція), і читаю дати із застереженнями: застосування з 28 червня 2025, відступ до 2027-го, перехід до 2030-го, старий вміст поза периметром за датою публікації.
Квіз
Перед стартом
- Питань: 16
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Навіщо на сторінці секційні елементи nav, footer, article?

