Хабр, я снова пришёл к вам с практическими советами про доступность вместе с Ильей. Мы показываем, как 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. Если вы хотите больше узнать о цифровой доступности, пишите мне в ТГ. Ссылка в профиле.


Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (13)


  1. ifap
    26.03.2024 11:50

    <label class="field__hint">E-mail</label>
    <input type="email" class="field__input">
    1. Зачем кому-то оборачивать ярлык в label и потом не связывать его с соответствующим input? Ошибка верстки? Так давайте тогда еще и незакрытые теги сюда добавим - тоже проблема.

    2. Явно указанный тип input'a (email), мягко говоря, снимает остроту проблемы.


    1. melnik909 Автор
      26.03.2024 11:50

      Зачем кому-то оборачивать ярлык в label и потом не связывать его с соответствующим input? Ошибка верстки?

      Нет. Разрешенный стандартом способ связи поля ввода и метки


      1. ifap
        26.03.2024 11:50

        ?! Каким именно образом в этом примере связаны label и input? Вы вроде как именна на отсутствие связи между ними и сетовали.


        1. melnik909 Автор
          26.03.2024 11:50

          Я не правильно прочитал предыдущее сообщение.

          Ошибка верстки? Так давайте тогда еще и незакрытые теги сюда добавим - тоже проблема.

          Я пишу о том, с чем встречаюсь в своей жизни. Если это ошибка на уровне незакрытого тега, то я и о ней напишу. Я не делаю приоритеты ошибкам. Для меня ошибка это ошибка.


  1. pantsarny
    26.03.2024 11:50

    Как в numeric можно ввести серию и номер паспорта?


    1. vadimk91
      26.03.2024 11:50

      Вот буквально на днях покупал авиабилет, поле одно, ввел серию и номер через пробел (на компьютере) - в результате слиплось вместе. Но если в российских паспортах серия и номер только цифры, то в документах других стран вполне могут быть и буквы.


  1. kspshnik
    26.03.2024 11:50

    А какие есть тонкости для модалки, которая не является диалогом/формой? Например, показывает картинку/текст (условно)? И влияет ли способ открытия модалки ( display или .append() , например) ?


    1. melnik909 Автор
      26.03.2024 11:50

      Например, показывает картинку/текст (условно)? 

      Это зависит не только от какой в контент в модалке, но и в каком контексте находится пользователь. Какие шаги до этого делал. Что ожидает.

      И влияет ли способ открытия модалки

      По моему опыту, не влияет.


      1. kspshnik
        26.03.2024 11:50

        Это зависит не только от какой в контент в модалке, но и в каком контексте находится пользователь. Какие шаги до этого делал. Что ожидает.

        Например, кликнул по картинке (картинка обёрнута кнопкой или проставлена aria-role ) в галерее.


        1. melnik909 Автор
          26.03.2024 11:50

          На сайте Etsy посмотрите пример


  1. bubn0ff
    26.03.2024 11:50

    Спасибо за статью, познавательно. inputmode="numeric" - это интересный вариант.


  1. dprotopopov
    26.03.2024 11:50

    Полезное дело!!!


  1. spravka-region
    26.03.2024 11:50

    На сайте вместо связки label+input, есть input с атрибутом placeholder="Название"... Это плохая практика ?