Хабр, я рассказал вам, как создавать подсказки с помощью атрибута 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
:
- он отлично подходит для скрытия визуальных декоративных элементов;
- использовать атрибут нужно так, чтобы не ухудшить опыт пользователя скринридера;
- не всегда интерактивные элементы будут скрыты;
- лучше не скрывать интерактивные элементы, а разрабатывать новые паттерны взаимодействия всей командой.
Если у вас возникли вопросы, то я буду ждать вас в комментариях. Большое спасибо за чтение.
Помоги спутнику бороться с космическим мусором в нашей новой игре! ????