Хабр, я снова пришёл к вам с практическими советами про доступность вместе с Ильёй. Мы показываем, как HTML и CSS могут улучшить или ухудшить её. Напоминаю, что Илья — мой незрячий знакомый, который помогает мне найти наши косяки в вёрстке.


Сегодня мы рассмотрим следующие аспекты:

  • какие скрытые проблемы с паттерном «visually-hidden» нас ждут;
  • в каких ситуациях кнопка «Закрыть» указывает на выход;
  • чем вредно значение contents у свойства display;
  • почему подсказка с помощью атрибута aria-label вызывает недоумение.

Давайте начнём!


▍ Нежданчик с паттерном «visually-hidden»


Я занимаюсь цифровой доступностью 6 лет, и всё это время существует паттерн «visually-hidden». Скорее всего, вы его видели. Данная техника представляет собой набор свойств, позволяющих только скрыть визуально элемент. Вот они:


.visually-hidden {
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

Для демонстрации принципа работы я добавлю его для элемента <h1>.


<body>
  <h1 class="visually-hidden">Дизайнер пользовательского интерфейса Стас Мельников</h1>
</body>

Так я скрыл визуально элемент, но для скринридера он останется доступен. Он скажет: «Заголовок уровень один. Дизайнер пользовательского интерфейса Стас Мельников». Вроде всё отлично. Но есть момент. Вы знаете, что будет, если использовать паттерн для части текста?


Например, представим, что имя и фамилия должно визуально отобразиться, а текст «дизайнер пользовательского интерфейса» должен быть доступен только скринридерам. Для этого нужно его обвернуть элементом с классом .visually-hidden.


<body>
  <h1 class="logo">
    <span class="visually-hidden">Дизайнер пользовательского интерфейса</span>
    <span class="logo__firstname">Стас</span>
    <span class="logo__lastname">Мельников</span>
  </h1>
</body>

Скринридер NVDA во всех браузерах озвучит текст за раз, кроме браузера Firefox. Для полного прочтения пользователю нужно нажать клавишу со стрелкой вниз () два раза. После первого нажатия он скажет: «Заголовок уровень один. Дизайнер пользовательского интерфейса». После второго: «Заголовок уровень один. Стас Мельников».


Почему так? Дело в том, что в паттерне используется position: absolute. Значение absolute добавляет для элемента display: block. А скринридер считывает такие элементы отдельно.


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


<body>
  <h1 class="logo">
    <span class="visually-hidden" role="presentation">Дизайнер пользовательского интерфейса</span>
    <span class="logo__firstname">Стас</span>
    <span class="logo__lastname">Мельников</span>
  </h1>
</body>

Значение presentation скрывает элемент, но оставляет доступным его контент. Таким образом, скринридер увидит только текст в элементе <h1>.


▍ Кнопка «Закрыть» случайно прогоняет пользователя скринридера


В своих статьях я стараюсь показать, что разработчикам нужно быть внимательными при вёрстке. Случайно сделал неверный порядок элементов, сразу страдает доступность. Очередным примером будет модальное окно.


Рассмотрим следующую разметку:


<body>
  <dialog class="modal">
    <button type="button" aria-label="Закрыть">
      <!-- здесь иконка --->
    </button>
    <!-- здесь контент модального окна -->
  </dialog>
</body>

Как думаете, где здесь проблема? После открытия модального окна кнопка «Закрыть» будет первым элементом, на который попадёт пользователь при нажатии клавиши со стрелкой вниз () или Tab. Илья говорит, что такое поведение негативно настраивает пользователя.


«Таким образом ты настраиваешь пользователя, что это главное действие на этом экране. Что маловероятно. У меня есть аналогия из бытовой жизни. Заходишь в магазин и сразу видишь стрелку к выходу. Ощущение, что тебя там не ждут и хотят, чтобы быстрее ушёл».


Конечно, не все пользователи нажмут на кнопку в такой ситуации. Как говорит Илья, они попробуют поискать то, что им нужно.


«Сам факт фокуса на «Закрыть» вызывает подозрение, что с окном что-то не так. Я попробую табать вниз, чтобы посмотреть, есть ли там функционал, или экран пустой».


В итоге ошибка в порядке элементов приводит к тому, что люди тратят дополнительные усилия, чтобы выполнить свою задачу. По этой причине я предлагаю располагать кнопку «Закрыть» последним элементом в модальном окне.


<body>
  <dialog class="modal">
    <!-- здесь контент модального окна -->
    <button type="button" aria-label="Закрыть">
      <!-- здесь иконка --->
    </button>
  </dialog>
</body>

Тем более такая позиция позволяет использовать жест перехода к последнему элементу в скринридере TalkBack на платформе Android. Это мне подсказал тоже Илья.


display: contents для интерактивных элементов


На сегодняшний день CSS позволяет нам скрыть элемент с сохранением его контента с помощью значения contents для свойства display. Допустим, мы применили его для элемента <div>.


<body>
  <div class="container">
    <button type="button">Кнопка</button>
  </div>
</body>

body {
  display: grid;
}

.container {
  display: contents;
}

Включен режим, отображающий грид-контейнер. Кнопка растянута на всю его ширину.

Стили элемента <body> стали действовать на кнопку, которая встала на место скрывшегося элемента <div>. По этой причине мы видим, что она растянулась на всю ширину элемента <body>. В этом заключается вся мощь значения contents. И в этом же скрыта большая проблема.


А что будет, если применить значение для элемента <button> или <a>? Давайте посмотрим.


<body>
  <button class="control" type="button">Кнопка</button>
  <a href="#0" class="control">Ссылка</a>
</body>

body {
  display: grid;
}

.control {
  display: contents;
}

Кнопка и ссылка внешне похожи на простой текст

Мы видим текст. Кнопка потеряла все стили. У ссылки остался только цвет. Вроде всё нормально. Где подвох?


Признаюсь. Когда я первый раз увидел эту демонстрацию, у меня была эйфория, и я подумал: «Вроде классный способ сбросить стили». Хорошо, что потом задумался о том, а можно ли взаимодействовать с такими элементами?


Сначала я нажал клавишу Tab. Тут ждало меня разочарование. У меня не получилось сфокусироваться на элементы. Это означает, что скринридеры тоже не найдут их, если пользователи используют Tab. А также для них останется доступен только текст, а сами элементы — нет. В итоге пришлось отказать от идеи. И это хорошо!


А потом я узнал, что кнопки и ссылки — не единственные элементы, для которых не стоит применять display: contents. Adrian Roselli подробнее рассказал в своём исследовании. Я не вижу смысла делать пересказ в этой статье. Лучше сами прочтите. Там много интересного.


Завершу раздел своим личным выводом. Не стоит отказываться от display: contents. Значение часто выручает. Лучше использовать его для элементов <div> и <span>. Скринридеры для этих элементов не делают подсказок, следовательно, отменить их не получится. Поэтому очень маленькая вероятность навредить пользователям.


▍ Как не перестараться с атрибутом aria-label


Я много общаюсь на тему цифровой доступности с разработчиками. Заметил одну проблему, которая появилась от большого желания людей сделать интерфейс доступным. Она есть в следующем коде:


<body>
  <a href="/logout" aria-label="Кнопка выйти">
    <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
      <path d="M24 20v-4h-10v-4h10v-4l6 6zM22 18v8h-10v6l-12-6v-26h22v10h-2v-8h-16l8 4v18h8v-6z"/>
    </svg>
  </a>
</body>

Чтобы понять ошибку, нужно услышать, как скринридер озвучит ссылку. А сделает он вот так: «Кнопка выйти, ссылка».


Илья говорит, что такие элементы ставят в ступор:


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


Конечно, я нажму на такой элемент. Но у меня не будет уверенности в том, что будет дальше. Этот элемент неоднозначный для меня. Он как чёрный ящик. Поэтому мне приходится включать опыт, знания и фантазию».


Как же исправить ошибку? Нужно помнить, что для большинства HTML-элементов уже определены подсказки, сообщающие скринридерам назначение элементов. По этой причине они знают, что элемент <a> является ссылкой.


Таким образом не надо использовать слова, которые уже используются в подсказках. Например, слова «Кнопка», «Ссылка», «Меню»», «Навигация». А уж тем более не надо добавлять слова противоречащие значению HTML-элемента. Скринридеры сами правильно расскажут про элемент, если вы используйте его правильно.


Осталось убрать из нашего примера слово «Кнопка».


<body>
  <a href="/logout" aria-label="Выйти">
    <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
      <path d="M24 20v-4h-10v-4h10v-4l6 6zM22 18v8h-10v6l-12-6v-26h22v10h-2v-8h-16l8 4v18h8v-6z"/>
    </svg>
  </a>
</body>

▍ Заключение


С помощью этой статьи мы с Ильёй хотели призвать вас:

  • если часть текста скрыта через паттерн, добавить к элементу role="presentation";
  • следить за тем, чтобы кнопка «Закрыть» была последним элементом в модальном окне;
  • использовать display: contents с осторожностью;
  • не использовать в атрибуте aria-label слова, описывающие тип элемента.

Оставлю ссылки на все выпуски:

Также нам будет интересен и ваш опыт. Делитесь своими кейсами в комментариях. Спасибо за чтение.


P.S. Если вы хотите больше узнать о цифровой доступности, добавляйтесь в мой ТГ канал. Ссылка в профиле.


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

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


  1. kurozetsu
    22.05.2024 10:48
    +1

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


  1. ifap
    22.05.2024 10:48

    Почему так? Дело в том, что в паттерне используется position: absolute.

    Нет, это потому, что тэги заголовков h1-h6 предусматривают внутри себя лишь фразовый контент, т.е. текст и его оформление уровня параграфа. Т.е. воткнуть внутрь заголовка какой-нибудь <b> (что само по себе не слишком феншуйно) стандарт позволяет, а <span> - нет. В итоге сперва браузер разбирает этот говнокод в соответствие с собственным представлением о прекрасном, а не о стандарте, затем кринридер переваривает это мнение и выдает свою версию.

    Илья говорит, что такие элементы ставят в ступор

    Ну еще бы: тут и зрячий без бутылки не разберется, зачем эти извращенцы набыдлокодили кнопку через a когда именно для таких целей есть button...


    1. TNK
      22.05.2024 10:48
      +1

      Так <span> же входит во phrasing content


      1. ifap
        22.05.2024 10:48

        Черт, и впрямь... Не помните, всегда входил или это очередной "живой" HTML?


        1. TNK
          22.05.2024 10:48

          Всегда входил