Хабр, я снова пришёл к вам с практическими советами про доступность вместе с Ильей. Мы показываем, как HTML и CSS могут улучшить или ухудшить её. Напоминаю, что Илья мой незрячий знакомый, который помогает мне найти наши косяки в вёрстке.
Сегодня мы рассмотрим следующие аспекты:
- К чему приводят распространённые ошибки с элементом
<label>
; - Лучший лайфхак с
inputmode="numeric"
улучшающий мою жизнь; - Как пользователи скринридера понимают, что модальное окно открыто.
Давайте начнём!
▍ Что может пойти не так при использовании элемента <label>
Каждое поле ввода должно иметь связанный с ним элемент <label>
. Данная мысль есть в подавляющем большинстве статьёй о доступности. Мне, конечно, не хочется дублировать то, что вы уже, скорее всего, прочитали. Что ещё можно добавить?
По моему мнению не хватает описания бытовых проблем, к которым приводит некорректная работа с элементом <label>
. Хорошо, что я знаком с Ильёй. Он мне всё рассказал. Теперь я могу передать существующие проблемы вам.
Начнём мы с примера, где добавили элемент <input>
без элемента <label>
.
<body>
<form>
<!-- Элементы формы --->
<div class="field">
<input type="text" class="field__input">
</div>
<!-- Элементы формы --->
</form>
</body>
Сейчас скринридеры найдут поле для ввода, но для них оно безымянное. Я спросил Илью, что он делает в такой ситуации:
«Если нет лейбла, то при табуляции с предыдущего поля на следующее ридер не читает его название. Чтобы понять, что я должен делать с таким неназванным полем, мне нужно нажать клавишу Esc, выйти из состояния ввода и вернуться стрелкой на шаг назад, чтобы прочитать название, написанное рядом.»
Первое, что меня заинтересовало в мысли Ильи, что он ищет подсказку перед полем для ввода. Я уточнил у него этот момент.
«Да, это привычка, оставшаяся от олдскульных приёмов реализации, когда названия прямо в полях ещё не встречались. Поэтому интуитивно ищу названия перед. Память от визуального опыта, который у меня тоже есть, подсказывает то же самое.»
Отсутствие элемента <label>
у поля, это самая критичная ситуация. А вот если он есть, но несвязан это чуть лучше. Хотя, не намного. Поскольку элемент <label>
не связан с полем, то для скринридера поле остаётся безымянным.
<body>
<form>
<!-- Элементы формы --->
<div class="field">
<label class="field__hint">E-mail</label>
<input type="email" class="field__input">
</div>
<div class="field">
<label class="field__hint">Телефон</label>
<input type="text" class="field__input" inputmode="numeric">
</div>
<!-- Элементы формы --->
</form>
</body>
Такой код приводит к тому, что Илья выходит из режима редактирования. Возвращается назад. Если повезёт, то сразу найдёт текст, который с большой вероятностью, но не сто процентной, будет подсказкой. Далее он возвращается к полю для ввода и входит в режим редактирования. Четыре действия вместо одного!
Представим обычную форму регистрации из полей для ввода имени, фамилии, даты рождения, электронной почты и двух полей для ввода и повторения пароля. Пять полей и 20 действий. У тут выбор, смириться или плюнуть на всё это.
«Это неудобно для форм из двух или трёх полей, где есть имя, телефон и электронная почта. Для больших форм анкетного типа уже критично. Время и когнитивная нагрузка на заполнение возрастают в разы.»
Когда мы обсуждали позицию подсказки, мне в голову пришёл вопрос: «А что будет в случае, когда подсказка будет находиться после поля ввода, но также не будет связана с ним?»
<body>
<form>
<!-- Элементы формы --->
<div class="field">
<input type="email" class="field__input">
<label class="field__hint">E-mail</label>
</div>
<div class="field">
<input type="text" class="field__input" inputmode="numeric">
<label class="field__hint">Телефон</label>
</div>
<!-- Элементы формы --->
</form>
</body>
Илья назвал этот случай самым неприятным вариантом.
«Самый неприятный для меня вариант, когда названия ставятся после полей. Зрячий может соотнести их правильно, а незрячий соотнесёт текущее название со следующим полем. В результате введёт электронную почту в поле для телефона или что-то подобное.»
Дело не исправляет даже связывание элемента <label>
с полем.
<body>
<form>
<!-- Элементы формы --->
<div class="field">
<input id="email" type="email" class="field__input">
<label for="email" class="field__hint">E-mail</label>
</div>
<div class="field">
<input id="tel" type="text" class="field__input" inputmode="numeric">
<label for="tel" class="field__hint">Телефон</label>
</div>
<!-- Элементы формы --->
</form>
</body>
Для понимания проблемы нужно рассказать про нюанс скринридера NVDA. Если он попал на поле ввода с помощью клавиш стрелок, то пользователь услышит: «Редактор». Всё.
То есть на этом этапе неважно есть ли связанная подсказка. Пока пользователь не войдёт в режим редактирования, нажав клавиши Space
или Enter
, скринридер её не озвучит. По этой причине важна позиция элемента <label>
.
При переключении клавишами стрелок пользователь предварительно погружается в правильный контекст. В нашем случае этого не происходит, поскольку позиция не правильная. Это приводит к тому, что пользователь попадает на первое поле ввода, не зная какое оно. А когда переходит ко второму, то он думает, что оно для ввода электронной почты.
В итоге самый лучший вариант это использовать связанный с полем элемент <label>
перед элементом <input>
.
<body>
<form>
<!-- Элементы формы --->
<div class="field">
<label for="email" class="field__hint">E-mail</label>
<input id="email" type="email" class="field__input">
</div>
<div class="field">
<label for="tel" class="field__hint">Телефон</label>
<input id="tel" type="text" class="field__input" inputmode="numeric">
</div>
<!-- Элементы формы --->
</form>
</body>
Существует также вариант, добавления элемента <input>
в элемент <label>
.
<body>
<form>
<!-- Элементы формы --->
<label class="field__hint">
<span class="field__hint">E-mail</span>
<input type="email" class="field__input">
</label>
<label class="field">
<span class="field__hint">Телефон</span>
<input type="text" class="field__input" inputmode="numeric">
</label>
<!-- Элементы формы --->
</form>
</body>
Я лично избегаю данную технику, потому что в сообществе говорили, что есть ситуации, когда скринридеры не определяют связь элементов. По моему мнению лучше использовать вариант с атрибутами for
и id
.
▍ Атрибут inputmode
позволяет мне меньше мучаться
При заполнении форм важным моментом является отображаемая клавиатура. Здесь, конечно, я сужу по себе. Я обладатель пальцев, которые больше чем клавиши на смартфоне. Данный факт усиливается тем, что из-за травмы у меня периодически тремор рук. Как результат, постоянно делаю опечатки.
Я понимаю, что у разработчиков сложная работа. Фреймворки, дедлайны и всё такое. Но, чёрт, почему при вводе численного типа данных мне отображается виртуальная клавиатура с буквами и маленькими цифрами?
Как-то я покупал авиабилеты. Я дошёл до поля для ввода серии и номера паспорта и увидел стандартную виртуальную клавиатуру. Жаль, здесь нельзя передать все мысли. Учитывая мою проблему с руками, я тогда знатно помучался.
Проблема заключается в том, что в коде используется значение text
для атрибута type
.
<body>
<label for="id_0746012685254408">Серия и номер</label>
<input type="text" title="Серия и номер" class="input__text-input" id="id_0746012685254408">
</body>
Для ввода текста это нормальный вариант, а для ввода численного типа данных — нет. Мне больше всего обидно, что разработчик этого приложения не подумал использовать специальную виртуальную клавиатуру с большими числами. Тем более делается это просто. Надо допечатать атрибут inputmode
и его значение numeric
.
<body>
<label for="id_0746012685254408">Серия и номер</label>
<input type="text" inputmode="numeric" title="Серия и номер" class="input__text-input" id="id_0746012685254408">
</body>
Поскольку я не могу изменить код с телефона, я вставил фрагмент кода в Codepen и сделал скриншот клавиатуры.
Дело на пять секунд. А пользовательский опыт получается в разы лучше!
▍ Открылось ли модальное окно
Модальное окно является элементом интерфейса, который появляется после нажатия по кнопке. Пользователи без ограничений по зрению привыкли видеть, как он появляется. Срабатывает визуальная ассоциация. А как пользователи скринридера понимают, что модальное окно открылось?
Первый момент. Пользователи скринридера знают, что модальное окно по умолчанию не отображается. Далее они взаимодействуют с приложением. Им встречается кнопка. Они нажимают на неё. Если услышат подсказку «Диалог», то она становится сигналом, что модальное окно открыто.
Нам нужно использовать атрибуты role
и aria-modal
, чтобы весь процесс работал корректно.
<body>
<div role="dialog" aria-modal="true" class="modal">
<!-- здесь дочерние элементы модального окна -->
</div>
</body>
Значение dialog
сообщает скринридеру, что элемент является диалогом. Стоп. В интерфейсах же используются диалоги разного типа. Легко запутаться. Что делать? Здесь на помощь приходит значение true
. Именно оно делает элемент модальным окном.
Данная техника использовалась до появления элемента <dialog>
. Теперь можно использовать его вместо элемента <div>
с атрибутом role
.
<body>
<dialog class="modal">
<!-- здесь дочерние элементы модального окна -->
</dialog>
</body>
Важный нюанс. У элемента <dialog>
есть два метода для открытия. Это .open()
и .showModal()
. Для модального окна следует использовать метод .showModal()
. В этом случае aria-modal="true"
установится автоматически.
▍ Заключение
С помощью этой статьи я с Ильёй хотел призвать вас:
- Добавлять к каждому полю для ввода информации подсказку, размеченную элементом
<label>
и связанную с ним; - Не важно, где внешне отображается подсказка для поля ввода данных, в коде она должна быть строго перед элементом
<input>
; - Связать элемент
<label>
лучше стоит через атрибутыfor
иid
; - Отображать пользователю более удобную для него виртуальную клавиатуру;
- Использовать элемент
<dialog>
для разметки модальных окон.
Оставлю ссылки на все выпуски:
Также нам будет интересен и ваш опыт. Делитесь своими кейсами в комментариях. Спасибо за чтение.
P.S. Если вы хотите больше узнать о цифровой доступности, пишите мне в ТГ. Ссылка в профиле.
Автор: Стас Мельников