ARIA-атрибуты (role, aria-label, aria-hidden и другие) нужны не всегда - и первое правило доступности звучит именно так: если для нужного смысла уже есть готовый HTML-тег, используй его, а не role. ARIA - это заплатка для нестандартных виджетов из div и span, которым HTML не может дать смысл сам по себе, а не замена уже семантичным тегам.
В этом уроке
Нажми Tab несколько раз внутри блокнота - фокус пройдёт и по настоящей кнопке, и по div с role="button" (у него есть tabindex="0"). Теперь удали tabindex="0" у div и попробуй снова - Tab будет проходить мимо неё, хотя роль "button" формально осталась.
Первое правило ARIA в HTML: не заменяй семантический тег атрибутом role
Первое правило ARIA дословно звучит так: если для нужного смысла и поведения уже есть родной HTML-тег - используй его, а не переделывай произвольный элемент через role. У тега button и роль "button", и обработка Enter/Space, и фокус по Tab уже встроены бесплатно. У div role="button" ничего этого нет по умолчанию: фокус и клавиатуру нужно добавлять вручную.
<button>Отправить</button> - роль, фокус и клавиатура работают сами, без единого дополнительного атрибута.
<div role="button">Отправить</div> - роль есть, но без tabindex элемент недоступен с клавиатуры, а Enter/Space придётся обрабатывать вручную через JS.
Когда ARIA-атрибуты в HTML действительно нужны: кастомные виджеты и иконки
ARIA нужна там, где родного тега для смысла просто нет: кастомные виджеты вроде выпадающего списка или вкладок, собранные из div, требуют role, aria-expanded, aria-selected. Кнопке без видимого текста (только иконка) нужен aria-label, чтобы у неё вообще было произносимое имя. А декоративную иконку рядом с текстом можно скрыть от скринридера через aria-hidden="true" - но ТОЛЬКО если элемент не может получить фокус: если поставить aria-hidden на ссылку или кнопку, пользователь скринридера долистает до неё Tab'ом, а она окажется невидимой и неозвученной.
<button aria-label="Закрыть"><span aria-hidden="true">×</span></button> - у кнопки есть произносимое имя, а декоративный символ скрыт от скринридера.
<a href="#" aria-hidden="true">Подробнее</a> - ссылка остаётся в фокусе по Tab, но для скринридера она как будто не существует: пользователь попадёт на невидимый, неозвученный элемент.
Частые ошибки новичков
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| Ставить role="button" на настоящий тег button | Роль уже встроена в button по умолчанию - атрибут просто лишний | Убрать role, тег и так объявляет нужную роль сам |
| Ставить role="button" на div без tabindex и обработки клавиш | Элемент получает роль, но недоступен с клавиатуры - пользователь без мыши не сможет им воспользоваться | Использовать настоящий button, либо вручную добавить tabindex="0" и обработку Enter/Space |
| Ставить aria-hidden="true" на элемент, который остаётся в фокусе (ссылка, кнопка) | Пользователь скринридера дойдёт до него Tab'ом, но он окажется невидимым и неозвученным | Не ставить aria-hidden на фокусируемые элементы - только на декоративный, нефокусируемый контент |
| Добавлять ARIA-атрибуты "на всякий случай" без проверки скринридером | По данным WebAIM Million, неправильная ARIA чаще увеличивает число ошибок, а не снижает | Проверять реальным скринридером, а не полагаться на то, что атрибут "выглядит правильно" |
Шпаргалка
| Термин | Когда использовать |
|---|---|
| Первое правило ARIA | Есть родной HTML-тег с нужным смыслом - используй его вместо role |
| role="button" на div | Нужно ещё вручную добавить tabindex="0" и обработку Enter/Space |
| aria-label | Даёт произносимое имя элементу без видимого текста (кнопка-иконка) |
| aria-hidden="true" | Скрывает декоративный контент от скринридера - никогда на фокусируемых элементах |
| aria-live | Объявляет скринридеру изменения контента без перезагрузки страницы |
Коротко: отвечаем на вопросы
Когда использовать ARIA вместо обычного HTML?
Только когда для нужного смысла или поведения нет родного HTML-тега - например, для кастомного виджета вроде вкладок или выпадающего списка, собранного из div. Если подходящий тег есть (button, nav, main и так далее) - первое правило ARIA требует использовать именно его, а не патчить произвольный элемент через role.
Работает ли aria-label, если у элемента уже есть видимый текст?
Технически да, но aria-label ПОДМЕНЯЕТ то, что объявит скринридер, вместо видимого текста - если они разойдутся, это собьёт с толку пользователя, который слышит одно, а видит другое. Если видимый текст уже есть, обычно лучше не трогать имя элемента вообще или использовать aria-labelledby, который явно ссылается на этот текст.
Почему aria-hidden элемент всё ещё в фокусе или озвучивается?
Это реальная и задокументированная ошибка: aria-hidden на родительском элементе не забирает фокусируемость у вложенных ссылок и кнопок. В результате пользователь скринридера долистает до элемента Tab'ом, а тот окажется невидимым и неозвученным для него. Правило простое: aria-hidden ставится только на нефокусируемый, декоративный контент.
Нужен ли role="button" на теге button?
Нет. У button эта роль уже встроена по умолчанию - добавлять role="button" поверх неё не имеет смысла и ничего не меняет.