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

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