Хабр, я рассказал вам, как создавать подсказки с помощью атрибута aria-label. Это отличный шаг к классному опыту для пользователей скринридера. А сегодня сделаем второй — мы научимся правильно скрывать элементы. Встречайте, атрибут aria-hidden.


▍ Что это такое?


О проблеме доступности цифровых интерфейсов задумались относительно недавно. Лично я изучаю её с 2016 года. А интерфейсы же проектировали ещё в 90-х годах. По этой причине подавляющее большинство, если не все, спроектированы для людей, видящих их. Графические элементы, механики взаимодействия, элементы управления — всё заточено под человека, который может увидеть их.


Небольшая, но значимая часть этого опыта в принципе не может подходить для пользователя скринридера. Тут возникает потребность скрыть то, что бесполезно, и заменить полезным и удобным. Здесь вотчина атрибута aria-hidden.


Он полностью скрывает элемент от скринридера. Чем-то этим напоминает атрибут hidden. Только есть разница. Когда элемент скрывается при помощи атрибута hidden, он не доступен скринридерам и его нельзя увидеть. При использовании атрибута aria-hidden он продолжает отображаться визуально.


Для скрытия элемента нужно использовать значение true, добавив атрибут с ним к элементу. Например, я сделал это для элемента <span>.


<body>
  <span aria-hidden="true">Этот текст не доступен для скринридера. Он не будет озвучен</span>
</body>

Теперь о тексте в элементе <span> знают только зрячие пользователи. Скринридером его прочитать нельзя.


▍ Используем на практике


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


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


Атрибут aria-hidden="true" упрощает взаимодействие пользователям скринридера. Часто нужно скрывать декоративные элементы. Звучит вроде просто, но всё не так однозначно. Для примера я взял заголовок видео одного популярного Youtube-канала.


<body>
  <span>Дзюба х Слуцкий х Роман Евгеньев</span>
</body>

Есть предположения, где здесь скрыта сложность? Буква «х». Авторы видео используют его в качестве декоративного разделителя. Для зрячего пользователя в этом нет проблем. Он пропустит символ и прочитает «Дзюба», «Слуцкий» и «Роман Евгеньев». Пользователи скринридеров не могут так. Они услышат текст полностью: «Дзюба ха Слуцкий ха Роман Евгеньев». Вот и думай причём тут «ха».


Исправить эту проблему просто. Нужно предоставить пользователям скринридеров такой же текст, какой видят зрячие. Они должны услышать: «Дзюба Слуцкий Роман Евгеньев». Для достижения этой цели скроем декоративные разделители с помощью aria-hidden="true".


<body>
  <span>Дзюба <span aria-hidden="true">х</span> Слуцкий <span aria-hidden="true">х</span> Роман Евгеньев</span>
</body>

Следующим примером полезного использования атрибута aria-hidden будут SVG-иконки внутри интерактивных элементов. Часто внутри элемента <svg> могут быть элементы <title>, <text>, <image> и <desc>. Для примера я добавил элемент <title> внутрь элемента <svg>.


<body>
  <button type="button">
    <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 26 26">
      <title>Стрелка</title>
      <polyline stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" points="19 11 13 5 7 11"></polyline>
      <path d="M13 5 L13 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
    </svg>
    <span>Перейти к началу страницы</span>
  </button>
</body>

Здесь скринридеры найдут иконку, а ещё в придачу текст «Стрелка». Конечно, всё расскажут пользователю. И такая же история будет с элементами <text>, <image> и <desc>.


Чтобы не спамить нашим пользователям, лучше скрыть декоративную иконку. Первое, что можно сделать, это удалить элемент <title>.


<body>
  <button type="button">
    <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 26 26">
      <polyline stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" points="19 11 13 5 7 11"></polyline>
      <path d="M13 5 L13 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
    </svg>
    <span>Перейти к началу страницы</span>
  </button>
</body>

Это поможет. Но часто бывает так, что в будущем элемент снова добавляют. Поэтому надёжнее скрыть железобетонно. Конечно, сделает это атрибут aria-hidden. Его нужно добавить к элементу <svg>.


<body>
  <button type="button">
    <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 26 26">
      <polyline stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" points="19 11 13 5 7 11"></polyline>
      <path d="M13 5 L13 21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
    </svg>
    <span>Перейти к началу страницы</span>
  </button>
</body>

Третьим примером полезного применения атрибута будет создание CSS-анимации. Бывает так, что в ней используется текст. Такой случай был у меня. Мне нужно было создать анимацию для лоадера, в котором анимировалось слово «Loading». Разметка была такой:


<body>
  <span class="cp-preloader" data-preloader-text="Loading...">Loading...</span>
</body>

Для создания эффекта я использовал свойство content. В нём я дублировал текст, чтобы он эффектно появлялся.


.cp-preloader {
  position: relative;
  color: rgba(0, 0, 0, 0.2);
}

.cp-preloader::before {
  content: attr(data-preloader-text);
  color: #222;
  position: absolute;
  inset: 0;
}

@media (prefers-reduced-motion: no-preference) {

  .cp-preloader::before {
    animation: cp-preloader34 6s ease-in-out infinite both;
  }

  @keyframes cp-preloader34 {
    0%, 10%, 90%, 100% {
      clip-path: circle(0 at 50% 50%);
    }
    40%, 60% {
      clip-path: circle(50vmax at 50% 50%);
    }
  }
}

Тогда я не знал, что скринридеры озвучивают текст из свойства content… В итоге они дважды говорили «Loading».


Я начал думать, как исправить. Ответ очевиден. Нужно вставить текст в HTML и сделать его недоступным для скринридеров. Задачка для атрибута aria-hidden.


<body>
  <span class="cp-preloader">
    <span class="cp-preloader__text" aria-hidden="true">Loading...</span>
    Loading...
  </span>
</body>

.cp-preloader {
  position: relative;
  color: rgba(0, 0, 0, 0.2);
}

.cp-preloader__text {
  color: #222;
  position: absolute;
  inset: 0;
}

@media (prefers-reduced-motion: no-preference) {

  .cp-preloader__text {
    animation: cp-preloader34 6s ease-in-out infinite both;
  }

  @keyframes cp-preloader34 {
    0%, 10%, 90%, 100% {
      clip-path: circle(0 at 50% 50%);
    }
    40%, 60% {
      clip-path: circle(50vmax at 50% 50%);
    }
  }
}

Так я сохранил анимацию и не навредил пользователям скринридера.


▍ Особенности использования


Мы уже узнали про значение true. Кроме него есть ещё два: false и undefined. Рассмотрим все их более подробно. У них куча нюансов!


Начнём с того, что добавим для элемента <span> только сам атрибут aria-hidden.


<body>
  <span aria-hidden>Этот текст доступен для скринридера?</span>
</body>

В этом случае браузер вместе со скринридером понимают значение атрибута как undefined. Итоговое решение о доступности элемента будет определено, опираясь на его стили. В теории в большинстве случаев всё будет хорошо.


Я провёл тест в скринридере NVDA и JAWS в трёх браузерах. Google Chrome, Firefox и Edge. Результаты одинаковые. Во всех браузерах текст озвучивается. Правда, есть несколько случаев, когда текст не будет доступен.


Первый — это использование CSS-свойства display со значением none.


<body>
  <span aria-hidden class="non-accessible-text">Этот текст недоступен для скринридера. Он не будет озвучен</span>
</body>

.non-accessible-text {
  display: none;
}

Второй случай является следствием первого. Атрибут hidden устанавливает display: none для элемента.


<body>
  <span aria-hidden hidden>Этот текст недоступен для скринридера. Он не будет озвучен</span>
</body>

Третий — это CSS-свойство visibility со значением hidden.


<body>
  <span aria-hidden class="non-accessible-text">Этот текст недоступен для скринридера. Он не будет озвучен</span>
</body>

.non-accessible-text {
  visibility: hidden;
}

Значение false. Оно сообщает, что элемент доступен для скринридера. На практике оно нужно очень редко. Лично я ни разу его не использовал. Нет смысла делать то, что уже сделано без моего участия.


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


И наконец значение true. Да, оно полностью скрывает элемент вместе с дочерними элементами, если они есть.


<body>
  <span aria-hidden="true">Этот текст недоступен для скринридера. Он не будет озвучен</span>

  <div aria-hidden="true">
    <span>Родительский элемент и этот текст тоже недоступны для скринридера</span>
  </div>
</body>

Правда здесь нас ждёт подвох. К сожалению, aria-hidden="true" сработает не для каждого элемента. Для интерактивных есть особенности. Попробуем добавить атрибут для элемента <button>.


<body>
  <!-- Элемент button будет скрыт не всегда -->
  <button aria-hidden="true" type="button">Перейти</button>
</body>

Если пользователь скринридера переключился на элемент <button> при помощи клавиш стрелок ( или ), то он будет недоступен. Но! Если это произойдёт при помощи клавиши Tab, то кнопка станет доступной. Тут начинается полная вакханалия.


Скринридеры ведут себя по-разному. JAWS в Google Chrome, Firefox и Edge будет озвучивать либо предыдущий, либо следующий элемент. Я не понял, от чего это зависит. NVDA в тех же браузерах скажет «Пусто». VoiceOver в Google Chrome и Safari вообще произнесут текст кнопки, как будто её не скрывали. А в Firefox она будет скрыта. В общем полный капут.


Такие же проблемы нас ждут, если добавить aria-hidden="true" к родительскому элементу, который содержит интерактивный элемент.


<body>
  <!-- Элемент button будет скрыт не всегда -->
  <div aria-hidden="true">
    <button type="button">Перейти</button>
  </div>
</body>

По этой причине строго нельзя использовать aria-hidden="true" для элементов, содержащих интерактивные элементы.


На такое поведение скринридеров может повлиять атрибут tabindex. Он может отключить интерактивность у элемента, и тогда он становится полностью недоступным. Для этого надо использовать значение -1.


<body>
  <!-- Элемент button будет скрыт всегда -->
  <button aria-hidden="true" tabindex="-1" type="button">Перейти</button>
</body>

Все скринридеры скроют кнопку. Только не будем спешить радоваться. Я говорил, что большинство интерфейсов разработаны для пользователя с мышкой. Есть функции, которые нужны ему, но полностью бесполезны для пользователей клавиатуры или скринридера. Здесь трюк с aria-hidden="true" tabindex="-1" будет полезен. Но это всё равно хак!


Самый лучший вариант переработать интерфейс так, чтобы были общие паттерны взаимодействия. Да, это дополнительная работа всей команды, но в этом случае результат будет лучше для пользователей.


▍ Заключение


Подведём итог. Ключевые моменты при работе с атрибутом aria-hidden:


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

Если у вас возникли вопросы, то я буду ждать вас в комментариях. Большое спасибо за чтение.


Помоги спутнику бороться с космическим мусором в нашей новой игре! ????

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