Доступные формы
Форма — место, где доступность важнее всего: здесь пользователь что-то отправляет, и одна неподписанная кнопка может сорвать всё действие.
Доступная форма — форма, в которой каждое поле имеет понятную подпись, ошибки объясняются текстом, а заполнить её можно с клавиатуры и со скринридером.
label и for — связка подписи с полем
Главное правило доступных форм: у каждого поля должна быть подпись <label>, программно связанная с этим полем. Без связи скринридер при попадании в поле скажет лишь «поле ввода» — без намёка, что туда писать.
Связь делается через пару for + id: атрибут for у <label> указывает на id поля.
<label for="email">Электронная почта</label>
<input id="email" type="email" name="email">
Бонус для всех пользователей: клик по такой подписи переносит фокус в поле (а у чекбокса — переключает его). Кликабельная зона становится больше — удобно и на десктопе, и на телефоне.
Обёртка label
Есть второй способ связать подпись и поле — обернуть поле в <label>. Тогда for и id не нужны, связь возникает по вложенности:
<label>
Я согласен с условиями
<input type="checkbox" name="agree">
</label>
Оба способа корректны. Обёртку часто берут для чекбоксов и радиокнопок, а пару for/id — для текстовых полей, где подпись и поле верстаются отдельно. Что нельзя — оставить поле совсем без подписи. И placeholder подписью не является: серый текст-подсказка исчезает, как только начинаешь печатать, и плохо озвучивается.
fieldset и legend — группировка связанных полей
Иногда несколько полей образуют единый вопрос. Классика — группа радиокнопок: у каждой своя подпись («Картой», «Наличными»), но всем нужен общий вопрос («Способ оплаты»). Для этого есть <fieldset> (рамка-группа) и <legend> (общий заголовок группы):
<fieldset>
<legend>Способ оплаты</legend>
<label><input type="radio" name="pay" value="card"> Картой</label>
<label><input type="radio" name="pay" value="cash"> Наличными</label>
</fieldset>
Скринридер при переходе на радиокнопку прочитает и legend, и подпись: «Способ оплаты, Картой, переключатель». Без fieldset пользователь услышит изолированное «Картой» и не поймёт, к какому вопросу это относится.
Обязательные поля
Чтобы пометить поле обязательным, используйте нативный атрибут required. Он не только включает встроенную проверку браузера, но и сообщается скринридеру как «обязательно»:
<label for="name">Имя</label>
<input id="name" name="name" required>
Важно не полагаться только на цвет или звёздочку. Если в дизайне обязательность показывают звёздочкой *, поясните её значение текстом («Поля со звёздочкой обязательны») — иначе незрячий или человек, не различающий цвета, не поймёт правило. Атрибут required доносит смысл независимо от визуального оформления.
Сообщения об ошибках через aria-describedby
Когда поле заполнено неверно, мало покрасить рамку в красный — цвет не воспринимается всеми. Нужно: показать текст ошибки, связать его с полем и пометить поле как ошибочное.
Текст ошибки привязывают к полю через знакомый по уроку об ARIA атрибут aria-describedby, а сам факт ошибки — через aria-invalid="true":
<label for="phone">Телефон</label>
<input id="phone" name="phone"
aria-invalid="true"
aria-describedby="phone-error">
<p id="phone-error">Введите номер в формате +7XXXXXXXXXX</p>
Теперь, попав в поле, скринридер объявит: «Телефон, поле ввода, неверно, Введите номер в формате...». Пользователь сразу понимает и что не так, и как исправить. Когда ошибка устранена, aria-invalid снимают (или ставят "false") из JavaScript.
Озвучивание ошибки в момент появления
Если ошибки появляются динамически после нажатия «Отправить», их полезно поместить в live-регион (aria-live="assertive" или role="alert"), чтобы скринридер прочитал их сразу, не дожидаясь, пока пользователь сам дойдёт до поля. role="alert" — это, по сути, готовый ассертивный live-регион.
<!-- role="alert" — скринридер озвучит текст, как только он появится -->
<p id="phone-error" role="alert">Введите номер в формате +7XXXXXXXXXX</p>
autocomplete — помощь браузера и пользователя
Атрибут autocomplete подсказывает браузеру, что это за поле, чтобы он мог предложить сохранённое значение (имя, адрес, e-mail). Для людей с моторными ограничениями или дислексией это огромная экономия сил: не нужно вручную набирать длинные данные.
<label for="fname">Имя</label>
<input id="fname" name="fname" autocomplete="given-name">
<label for="mail">Почта</label>
<input id="mail" type="email" name="mail" autocomplete="email">
<label for="tel">Телефон</label>
<input id="tel" type="tel" name="tel" autocomplete="tel">
Значения autocomplete стандартизированы (given-name, family-name, email, tel, street-address, postal-code и десятки других). Это часть WCAG: критерий «Identify Input Purpose» прямо требует помечать назначение полей через autocomplete.
Как это работает под капотом
Когда <label> связан с полем (через for/id или обёрткой), браузер при построении дерева доступности берёт текст подписи как имя поля. Скринридер читает именно его. aria-describedby добавляет к узлу описание (текст ошибки или подсказки), которое озвучивается после имени. aria-invalid выставляет в дереве состояние «ошибка», а required — состояние «обязательно». То есть все эти атрибуты не рисуют ничего нового на экране — они наполняют узел поля смыслом, который потом озвучивает скринридер. Без связи label↔поле имя у поля пустое, и весь остальной труд теряет смысл.
Частые ошибки
- Placeholder вместо label. Подсказка исчезает при вводе и не считается подписью — поле остаётся безымянным.
labelбез связи. Текст рядом с полем есть, но нетfor/idи нет обёртки — связи нет, скринридер подпись не прочитает.- Группы радиокнопок без
fieldset/legend. Непонятно, к какому вопросу относятся варианты. - Ошибка только цветом. Красная рамка без текста и без
aria-invalid— незрячий и не различающий цвета пользователь не узнает о проблеме. - Игнор
autocomplete. Поля без него заставляют каждый раз вручную набирать имя, адрес, телефон.
Итоги
- Каждое поле снабжайте
<label>— черезfor/idили обёрткой;placeholderподписью не является. - Группы связанных полей (радиокнопки) объединяйте в
<fieldset>с<legend>. - Обязательность задавайте атрибутом
required, не полагаясь только на цвет или звёздочку. - Ошибки показывайте текстом, связывайте через
aria-describedbyи помечайте полеaria-invalid="true"; для динамики —role="alert". - Назначение полей помечайте
autocomplete— это и помощь пользователю, и требование WCAG.