{ "index": 300, "slug": "editorial-2019-09-practice-accessibility-basics", "title": "Базовая доступность формы: имя, фокус и понятная ошибка", "excerpt": "Форма может работать мышью и всё равно оставлять пользователя без имени поля, видимого фокуса и объяснения ошибки. Разбираем короткий проверяемый маршрут и правки в нативном HTML.", "contentHtml": "
Форма выглядит готовой, пока её не проходят клавишей Tab. Фокус пропадает на светлом фоне. Поле с подписью «Почта» не получает понятного имени. После отправки появляется красная рамка, но причина ошибки остаётся неизвестной. Мышью такой экран ещё можно пройти. С клавиатурой пользователь теряет маршрут и не понимает, что исправить.
\nПредставим страницу профиля на учебном стенде. Разработчик сначала проверяет успешную отправку мышью и видит ожидаемый результат. Затем он проходит форму с клавиатурой: Tab перескакивает через поле, а при пустой почте фокус остаётся на кнопке. Это не один визуальный недочёт. Пользователь не видит текущий control, не получает его имя и не знает, какое значение исправить.
\nЦена ошибки растёт после первого релиза. Дефект повторяется на каждой форме с тем же компонентом. Поддержка получает вопросы о «неработающей кнопке». Команда добавляет случайные tabindex и ARIA-атрибуты, не понимая, какое состояние они меняют. Базовую доступность выгоднее проверять до распространения компонента: по одному действию, видимому симптому и повторяемому результату.
Для формы нужны три наблюдаемых свойства. Пользователь должен видеть, где находится фокус. Браузер должен вычислять для control имя, связанное с его назначением. Ошибка должна появляться текстом и быть связана с ошибочным полем. DOM помогает найти причину, но сам по себе не доказывает результат. Проверка начинается с живого маршрута клавиатуры, затем продолжается в панели Accessibility.
\nНативный HTML задаёт большую часть механизма. label связывает подпись с полем через пару for/id или через вложение control. button уже умеет получать фокус и активироваться клавишей. Браузер использует эти отношения, чтобы построить доступное представление текущей страницы. ARIA уточняет имя, описание и состояние, но не заменяет сломанный порядок DOM и не превращает произвольный div в готовую кнопку.
Ниже самостоятельный учебный пример для разбора в связке инструментов 2019 года. Он не отправляет данные на сервер и не заявляет результат для конкретного продукта или вспомогательной технологии. В примере разделены разметка, заметный стиль фокуса и обработчик ошибки: так видно, какое состояние меняется после неудачной отправки.
\n<style>\n input:focus, button:focus {\n outline: 3px solid #005fcc;\n outline-offset: 2px;\n }\n</style>\n\n<form id=\"profile-form\" novalidate>\n <label for=\"profile-email\">Рабочая почта</label>\n <p id=\"profile-email-hint\">На этот адрес придёт ссылка.</p>\n <input id=\"profile-email\" name=\"email\" type=\"email\" required\n aria-describedby=\"profile-email-hint profile-email-error\"\n aria-errormessage=\"profile-email-error\" aria-invalid=\"false\" />\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 value = email.value.trim();\n const message = value === ''\n ? 'Введите рабочую почту.'\n : email.validity.valid\n ? ''\n : 'Проверьте формат: name@example.com';\n setEmailError(message);\n if (message) email.focus();\n});\nВ разметке есть две разные связи. label даёт полю имя «Рабочая почта». aria-describedby перечисляет идентификаторы подсказки и места ошибки. Когда значение неверно, обработчик ставит aria-invalid=\"true\", показывает текст и возвращает фокус в поле. Пользователь получает причину и может сразу исправить значение.
Порядок имеет значение. Ошибка не должна существовать только в цвете рамки: человек с нарушением цветового восприятия может её не заметить, а вспомогательная технология не получит объяснения. Текст должен назвать проблему и, если это уместно, ожидаемый формат. Если приложение рисует общий summary, он тоже должен вести к ошибочному полю. Не оставляйте пользователя в верхней части страницы без понятного следующего шага. Синий цвет в CSS ниже — пример; его контраст и видимость нужно проверить на реальном фоне.
\n| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| После Tab не видно активный control | Убран outline или контраст фокуса слабый | Пройти маршрут на реальном фоне и наблюдать активный элемент | Вернуть заметный :focus-стиль и проверить его на светлой и тёмной поверхностях |
| Подпись видна, но имя поля пустое | Рядом стоит span, либо for не совпадает с id | Открыть поле в панели Accessibility и сравнить name с видимой подписью | Использовать связанный label; убрать дублирующие и расходящиеся имена |
| Кнопка доступна мышью, но не клавиатурой | Действие повесили на div без полной клавиатурной модели | Дойти до элемента Tab и активировать Enter или Space по правилам проекта | Заменить элемент на button или отдельно реализовать и проверить всю модель |
| После submit видна только красная рамка | Ошибка не выведена текстом или состояние не синхронизировано | Проверить текст, aria-invalid, описание и положение фокуса | Показать понятный текст, связать его с полем и выбрать один предсказуемый фокус |
| Tab прыгает в неожиданном порядке | Положительные значения tabindex отделили фокус от DOM | Записать порядок вперёд и назад, сравнить его с визуальным и смысловым порядком | Вернуть логичный DOM и убрать положительные номера, если для них нет строгой причины |
Каждая строка отделяет факт от догадки. Если в панели нет имени, сначала исправляйте связь label и control. Если имя есть, но сообщение об ошибке не читается в выбранной вспомогательной технологии, одной правки HTML недостаточно: нужно проверить динамическое обновление и выбранный API. Не подменяйте один результат другим.
\nDOM отвечает на вопрос, какие узлы создал код и в каком порядке. Клавиатурный маршрут отвечает, к каким узлам пользователь может прийти. Панель Accessibility показывает, какое имя, роль, описание и состояние браузер вычислил для выбранного элемента. Эти слои связаны, но могут расходиться.
\nНапример, такой фрагмент виден на экране, но не создаёт формальную подпись:
\n<span class=\"field-title\">Поиск</span>\n<input id=\"query\" type=\"search\" />\nИсправленный вариант задаёт связь явно:
\n<label for=\"query-good\">Поиск</label>\n<input id=\"query-good\" type=\"search\" />\nТа же граница действует для интерактивных элементов. <div tabindex=\"0\">Сохранить</div> только добавляет узел в последовательность фокуса. Он не даёт автоматически роли кнопки, активации Space, корректного состояния или поведения формы. <button type=\"button\">Сохранить</button> передаёт браузеру больше нужной семантики. Чем меньше самодельных правил, тем короче маршрут проверки.
label, порядок DOM, стиль фокуса или состояние ошибки. Не добавляйте ARIA наугад.Этот маршрут закрывает узкий риск: базовое управление формой с клавиатуры, понятное имя control и текстовую ошибку. Он не измеряет контраст, масштабирование, жесты, язык страницы, ловушку фокуса в диалоге и все критерии WCAG. Для них нужны отдельные сценарии.
\nПанель Accessibility зависит от браузера и версии. Её снимок показывает наблюдение конкретного user agent, а не универсальную гарантию для всех платформ. Наличие ARIA-атрибута тоже не доказывает полезный опыт. Вспомогательные технологии могут по-разному обрабатывать динамическую ошибку, поэтому «текст появился в DOM» и «сообщение было объявлено» — разные результаты.
\nЕсли поле проходит DOM-проверку, но пользователь со screen reader не получает ожидаемое сообщение, отрицательный путь не закрыт. Повторите сценарий на согласованной связке браузера и вспомогательной технологии. Зафиксируйте, что именно прозвучало и куда переместился фокус. Если такого прогона нет, честный вывод ограничивается проверкой разметки и поведения клавиатуры.
\nФорму можно считать прошедшей этот базовый контроль, если один и тот же человек повторяет сценарий без мыши: видит каждый переход фокуса, проходит элементы в смысловом порядке, активирует отправку, получает текстовую причину ошибки, видит ошибочное поле и возвращается к нему предсказуемо. В панели Accessibility у поля есть ожидаемые role, name, description и invalid state. Браузер и версия записаны рядом с результатом.
\nЭто не сертификат доступности и не обещание одинакового поведения на всех устройствах. Это узкий критерий, который можно проверить снова после изменения компонента. Если хотя бы один пункт не выполнен, форма остаётся в отрицательном пути: сначала исправьте наблюдаемый симптом, затем повторите весь маршрут.
\nfor/id или вложение.tabindex участвует в правилах фокусировки и не заменяет логичный порядок документа.aria-invalid и не должно оставаться скрытым, когда ошибка актуальна.