{ "index": 299, "slug": "editorial-2019-09-mechanism-accessibility-basics", "title": "Почему DOM не доказывает доступность: имя, роль и фокус", "excerpt": "В разметке есть input и кнопка, но пользователь всё равно может потерять фокус или не понять ошибку. Разбираем, как браузер превращает HTML в доступное представление и что проверить на живой форме.", "contentHtml": "
Симптом обычно появляется на готовом экране. Мышью форма работает: поле принимает текст, кнопка отправляет данные, красная рамка показывает ошибку. После перехода клавишей Tab фокус пропадает на фоне. У поля «Почта» нет понятного имени. После submit остаётся только цвет, а текст причины не связан с input. Цена ошибки — сорванная операция для пользователя и дорогое исправление общего компонента после того, как его скопировали на несколько страниц.
\nТезис простой: DOM — это вход для механизма доступности, а не его итог. Браузер читает порядок узлов, нативные элементы, подписи и ARIA-атрибуты. Затем строит представление с ролью, именем, описанием и состояниями. Пользователь и вспомогательная технология взаимодействуют с этим представлением через браузер. Поэтому наличие узла в инспекторе не доказывает, что элемент управления (control) получил имя, доступен с клавиатуры или объявляет актуальную ошибку.
\nУ механизма есть несколько последовательных слоёв. HTML создаёт элементы и связывает их отношениями. Браузер определяет, какие элементы интерактивны, в каком порядке они получают фокус и какую нативную роль имеют. Текущее состояние страницы меняет доступное представление: поле может стать недействительным, ошибка может появиться или исчезнуть, фокус может перейти к другому элементу управления. Затем браузер передаёт эти сведения платформенному accessibility API.
\nРазрыв возникает, когда проверяют только первый слой. Разработчик видит input с id и текст рядом с ним. Пользователь видит поле без подписи, если рядом стоит обычный span, а не связанный label. Разработчик видит обработчик click на div. Пользователь клавиатуры не получает нативную активацию, предсказуемый фокус и обязательное поведение кнопки. Разработчик видит элемент с aria-describedby. Пользователь не получает описание, если ссылка указывает на несуществующий ID или текст остаётся скрытым.
Видимая подпись и программное имя должны описывать один и тот же элемент управления. Для обычного поля начните с нативного HTML: у label должен быть атрибут for, равный id поля, либо элемент нужно вложить в label. Соседний span не создаёт такую связь. Уникальный id тоже не создаёт имя сам по себе.
<form id='profile-form' novalidate>\n <label for='profile-email'>Рабочая почта</label>\n <p id='profile-email-hint'>Ссылка для входа придёт на этот адрес.</p>\n <input\n id='profile-email'\n name='email'\n type='email'\n autocomplete='email'\n required\n aria-describedby='profile-email-hint profile-email-error'\n aria-errormessage='profile-email-error'\n aria-invalid='false'\n />\n <p id='profile-email-error' hidden></p>\n <button type='submit'>Сохранить</button>\n</form>\n\nconst form = document.querySelector('#profile-form');\nconst email = document.querySelector('#profile-email');\nconst error = document.querySelector('#profile-email-error');\n\nfunction setEmailError(message) {\n const invalid = Boolean(message);\n email.setAttribute('aria-invalid', String(invalid));\n error.hidden = !invalid;\n error.textContent = message;\n}\n\nform.addEventListener('submit', (event) => {\n event.preventDefault();\n const message = email.validity.valid\n ? ''\n : 'Введите адрес в формате name@example.com';\n setEmailError(message);\n if (message) email.focus();\n});\nПример учебный. Он не выполняет серверную проверку, не имитирует сеть и не доказывает, что конкретная вспомогательная технология произнесёт сообщение. Его задача — показать единый переход состояния. При ошибке меняются текст, видимость и aria-invalid. Фокус возвращается в поле после неудачной отправки. В реальном продукте сервер остаётся источником окончательного решения, а клиентский текст не должен обещать проверку, которой он не выполняет.
aria-describedby связывает элемент управления с подсказкой и текстом ошибки. Все ID должны существовать, а текст должен быть понятен без цвета. aria-errormessage указывает на сообщение об ошибке и используется вместе с aria-invalid. Атрибуты не создают содержимое и не исправляют неверную логику фокуса. Если error-элемент остаётся скрытым после ошибки или содержит только «Неверно», формальная связь не даёт следующего действия.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| После Tab не видно активный элемент | outline отключён без равноценного индикатора | Пройти маршрут на настоящем фоне и записать activeElement | Вернуть заметный :focus-стиль и проверить контраст индикатора |
| У поля нет понятного имени | Рядом стоит span или for указывает не на этот id | Сверить label/for/id и имя поля в Accessibility tree | Исправить нативную связь; не добавлять случайный aria-label |
| Кнопка работает только мышью | Действие повесили на div или span | Активировать Tab, Enter и Space в поддерживаемом браузере | Использовать button или отдельно реализовать весь клавиатурный контракт |
| Есть красная рамка, но нет объяснения | Ошибка выражена только CSS-классом | Отправить пустую форму и найти видимый текст рядом с полем | Вывести причину, связать её с полем и обновить состояние invalid |
| Порядок прыгает между блоками | Положительный tabindex отделил фокус от DOM-порядка | Сравнить Tab и Shift+Tab с визуальным порядком страницы | Собрать логичный DOM и убрать положительные значения tabindex |
Таблица помогает выбрать малую проверку, но не заменяет полный аудит. Она не измеряет контраст всего интерфейса, масштабирование текста, жесты, язык страницы или сложное модальное окно. Дерево доступности также не заменяет проверку со скринридером. Сначала фиксируйте наблюдение: браузер, версия, шаг, ожидаемое и фактическое состояние. Не записывайте «ошибка озвучивается», пока вы действительно не прошли этот сценарий с выбранной связкой браузера и вспомогательной технологии.
\nПорядок фокуса задаёт маршрут выполнения операции. Если пользователь после поля попадает на декоративный узел, скрытую кнопку или другой блок, он теряет модель страницы. WCAG требует сохранять смысл и работоспособность последовательной навигации. Поэтому сначала проверяйте порядок документа и нативные элементы. Положительный tabindex создаёт отдельный порядок и быстро ломается после добавления нового элемента управления.
Видимый фокус тоже функционален. CSS вроде outline: none допустим только вместе с равноценным индикатором. Индикатор должен быть различим на фоне и соседних состояний. На длинной форме проверьте, что фиксированная панель или прокрутка не закрывают сфокусированный элемент. Это отдельная проверка: наличие фокуса в DOM ещё не означает, что его можно увидеть.
label, input, button, существование ID и отсутствие положительных tabindex.aria-invalid и решение о следующем фокусе.Нативный HTML не делает сложный виджет доступным автоматически. Диалог, комбобокс, вкладки и drag-and-drop имеют дополнительные клавиатурные правила и состояния. Их нельзя закрыть копированием атрибутов из примера формы.
\nARIA не заменяет нативную семантику. Добавленный role='button' не превращает div в полноценную кнопку: автору всё равно нужно обеспечить фокус, активацию Enter и Space, состояние и доступное имя. Там, где подходит button, он уменьшает количество правил и поверхность ошибки.
Проверка одного браузера не доказывает совместимость со всеми браузерами и технологиями. Панель Accessibility показывает представление конкретной среды. Скринридер может иметь собственные особенности. Проверяйте поддерживаемые комбинации из матрицы проекта и разделяйте факты от предположений.
\nФорма готова к этому уровню проверки, если один человек может пройти выбранный сценарий без мыши и без угадывания. На каждом шаге виден фокус. У каждого элемента управления есть ожидаемые роль и имя. Подсказка и ошибка имеют понятный текст и действующие ID-связи. После неудачного submit пользователь попадает в заранее выбранное место и понимает следующий шаг. Эти факты записаны для конкретного браузера. Отдельно отмечено, какие проверки со скринридером ещё не выполнены.
\nТакой критерий не означает соответствие всей странице WCAG. Он закрывает один проверяемый контракт формы: HTML задаёт семантику, браузер строит доступное представление, клавиатура проверяет маршрут, а отрицательный путь проверяет состояние ошибки. Если любой из этих фактов не подтверждён, задача ещё не готова.
\nfor/id или вложение.