Знаете, что общего у большинства красивых кнопок в современных дизайн-системах и невидимых призраков? Они есть, но для некоторых пользователей их просто нет. Если вы когда-нибудь пытались купить билет на сайте банка с помощью клавиатуры или программы чтения с экрана (скринридера), то наверняка сталкивались с этим кошмаром. Курсор исчезает, фокус теряется, а голосовой помощник бубнит пустоту. Именно здесь на сцену выходят ARIA - набор атрибутов, которые позволяют разработчикам объяснять браузерам и вспомогательным технологиям, что именно делает тот или иной элемент интерфейса.
Но давайте сразу расставим точки над «i». ARIA не добавляет функциональности. Она не заставляет кнопку работать. Она лишь сообщает скринридеру: «Эй, это кнопка, и она открыта». Если сама логика сломана, никакие атрибуты её не починят. Однако, если вы строите сложные компоненты вроде выпадающих меню, табов или модальных окон, без ARIA ваш интерфейс станет непроходимым лабиринтом для людей с ограниченными возможностями здоровья.
Что такое ARIA и зачем она нужна
ARIA (Accessible Rich Internet Applications) - это спецификация от W3C, которая расширяет семантику HTML. Обычный HTML хорош, пока вы используете стандартные теги: <button>, <input>, <nav>. Но современная разработка часто уходит от них. Мы делаем кастомные дропдауны из <div> и <span>, потому что так удобнее стилизовать. Проблема в том, что браузер считает эти элементы просто текстом. Скринридер скажет: «Текст: Выпадающее меню», вместо «Кнопка: Выпадающее меню, свернуто».
ARIA решает эту проблему через три основных механизма:
- Роли (Roles): Говорят, чем является элемент (кнопкой, ссылкой, вкладкой).
- Свойства (Properties): Описывают характеристики элемента (например,
aria-labelдля скрытых подписей). - Состояния (States): Показывают текущий статус (активна ли вкладка, открыт ли диалог).
Важно помнить первое правило использования ARIA: лучше вообще ничего не делать, чем делать неправильно. Лишние или противоречивые атрибуты могут запутать скринридер сильнее, чем отсутствие таковых.
Основные категории ARIA-атрибутов
Не стоит пытаться заучить весь список из спецификации. Для 90% задач вам хватит понимания нескольких ключевых групп. Давайте разберем их на конкретных примерах из реальной практики.
Роли: определяем назначение элемента
Роль говорит вспомогательной технологии, как интерпретировать элемент. Например, если вы сделали переключатель (toggle) из <div>, браузер видит его как блок текста. Добавьте role="switch", и скринридер объявит его как переключатель.
Однако будьте осторожны. Использование роли button на элементе, который не поддерживает фокус и события мыши, создаст ложное ожидание. Пользователь услышит «кнопка», попытается нажать Enter, а ничего не произойдет. Поэтому роль всегда должна сопровождаться соответствующим поведением JavaScript.
Свойства: даем контекст
Самое популярное свойство - aria-label. Оно заменяет видимый текст на более понятный для скринридера. Представьте иконку поиска без подписи. Без ARIA скринридер промолчит или прочитает код SVG. С aria-label="Поиск по сайту" он четко озвучит действие.
Есть нюанс: aria-label переопределяет содержимое элемента. Если внутри кнопки есть текст «Найти», а вы поставите aria-label="Отправить форму", пользователь услышит только второе. Это может быть полезно, но требует аккуратности. Альтернатива - aria-labelledby, которое ссылается на ID другого элемента. Это предпочтительнее, если подпись находится вне компонента, например, в заголовке секции.
Состояния: показываем динамику
Интерфейсы меняются. Вкладки открываются, меню сворачиваются. Браузер не знает об этом сам. Здесь нужны состояния:
aria-expanded="true/false": для аккордеонов и дропдаунов.aria-selected="true/false": для активной вкладки или опции в списке.aria-hidden="true": чтобы спрятать декоративные элементы от скринридеров.
Без обновления этих состояний в JS интерфейс будет казаться «застрявшим» для незрячих пользователей. Они будут слышать, что меню свернуто, даже когда оно уже развернулось на экране.
Типичные ошибки при использовании ARIA
Многие разработчики думают, что добавление пары атрибутов автоматически делает сайт доступным. На деле же часто совершают одни и те же ошибки.
| Ошибка | Последствия | Решение |
|---|---|---|
Использование role="button" на <div> без tabindex |
Элемент недоступен с клавиатуры | Добавьте tabindex="0" и обработчик событий Enter/Space |
| Противоречие между HTML и ARIA | Непредсказуемое поведение скринридеров | Уберите ARIA, если есть нативный тег (<button>) |
Избыточные aria-label |
Дублирование информации («Кнопка: Кнопка») | Используйте только если визуальный текст отсутствует или непонятен |
| Игнорирование управления фокусом | «Ловушка фокуса» или потеря ориентации | Реализуйте логику перемещения фокуса при открытии/закрытии модалок |
Особенно опасен пункт про противоречия. Если вы напишете <button role="link">, вы скажете браузеру: «Это ссылка, но я хочу, чтобы ты считал её кнопкой». Некоторые скринридеры сойдут с ума, другие выберут один вариант. Всегда отдавайте предпочтение нативному семантическому HTML. ARIA - это костыль, а не протез нового поколения.
Практический пример: создание доступного аккордеона
Давайте соберем простой компонент, который часто ломается в дизайн-системах. У нас есть заголовок, который кликом открывает контент ниже.
Неправильный вариант (без ARIA):
<div class="accordion-header" onclick="toggle()">
Условия доставки
</div>
<div class="accordion-body" style="display:none">
...текст...
</div>
Скринридер прочитает заголовок как обычный текст. Пользователь не поймет, что это интерактивный элемент.
Правильный вариант с ARIA:
<button aria-expanded="false" aria-controls="panel-1" id="header-1">
Условия доставки
</button>
<div id="panel-1" role="region" aria-labelledby="header-1" hidden>
...текст...
</div>
Что здесь происходит?
- Мы используем нативный
<button>, поэтому нам не нужно добавлятьrole="button"и возиться с фокусом. aria-expanded="false"говорит скринридеру, что панель сейчас закрыта. При клике JS должен менять это значение наtrue.aria-controls="panel-1"связывает кнопку с областью контента. Это помогает некоторым продвинутым скринридерам навигировать напрямую к содержимому.- Атрибут
hidden(или CSSdisplay: none) реально скрывает контент. Важно: скрывать контент визуально недостаточно, нужно убирать его из DOM или использоватьaria-hidden="true", иначе скринридер будет читать скрытый текст.
Инструменты для проверки доступности
Вы не можете проверить ARIA глазами. Вам нужны инструменты. Вот мой рабочий набор для проектов в Казани и удаленных заказчиков:
- axe DevTools: Расширение для Chrome/Firefox. Автоматически находит грубые ошибки, такие как отсутствие лейблов или конфликтующие роли.
- Lighthouse: Встроен в Chrome. Дает общую оценку доступности, но часто дает ложноположительные результаты для сложных компонентов.
- NVDA / JAWS: Настоящие скринридеры. NVDA бесплатен и отлично работает на Windows. JAWS платный, но является стандартом в корпоративном секторе. Обязательно тестируйте свои компоненты хотя бы в NVDA.
- VoiceOver: Если вы ориентируетесь на macOS/iOS, встроенный VoiceOver обязателен к проверке. Он ведет себя иначе, чем десктопные аналоги.
Помните: автоматические тесты ловят около 30-40% проблем. Остальное можно найти только ручным тестированием с включенным звуком.
ARIA в React и других фреймворках
Если вы пишете на React, Vue или Angular, следите за тем, как атрибуты пробрасываются в DOM. В React, например, атрибуты ARIA пишутся в camelCase в коде JSX (ariaLabel), но рендерятся в нижнем регистре (aria-label) в итоговом HTML. Забудьте про это различие, и браузер вас не поймет.
Также обратите внимание на библиотеки компонентов. Многие готовые UI-киты (MUI, Ant Design, Bootstrap) уже имеют встроенную поддержку ARIA. Не изобретайте велосипед. Изучите документацию выбранной библиотеки на предмет пропсов вроде a11yProps или accessibilityAttributes. Часто проблема не в отсутствии поддержки, а в том, что разработчик не передает нужные значения в эти пропсы.
Чек-лист перед релизом
Перед тем как отправить задачу в ревью, прогоните этот короткий список:
- Все интерактивные элементы доступны с клавиатуры?
- Фокус виден на экране (нет
outline: noneбез замены)? - Скринридер правильно называет кнопки и ссылки?
- Статусы динамических элементов (меню, тултипы) обновляются в реальном времени?
- Нет ли лишних
aria-label, дублирующих видимый текст? - Проверено ли поведение в одном реальном скринридере (NVDA/VoiceOver)?
Доступность - это не галочка в чек-листе юристов. Это уважение к пользователю. Когда вы правильно используете ARIA, вы делаете интерфейс честным. Он говорит правду о своем состоянии и назначении всем, кто пытается им воспользоваться, независимо от того, смотрят они на экран или слушают его.
Нужно ли добавлять ARIA ко всем элементам страницы?
Нет, и это главная ошибка новичков. Нативные HTML-теги (<button>, <a>, <input>) уже имеют встроенную семантику. Добавление role="button" к тегу <button> избыточно и может создать конфликты. Используйте ARIA только тогда, когда стандартного HTML недостаточно для описания сложного компонента.
Чем отличается aria-label от aria-labelledby?
aria-label содержит текстовую строку непосредственно в атрибуте. aria-labelledby ссылается на ID другого элемента на странице, текст которого используется как метка. Второй вариант предпочтительнее, так как позволяет поддерживать локализацию и обновление текста в одном месте, а также использовать более длинные описания.
Как проверить, что скринридер действительно читает мои атрибуты?
Самый надежный способ - включить NVDA (бесплатно для Windows) или VoiceOver (macOS/iOS) и пройти по странице с помощью Tab. Слушайте, как озвучиваются элементы. Если вы видите ошибку в axe DevTools, но скринридер читает всё корректно, возможно, предупреждение инструмента ложное. И наоборот: если скринридер молчит или путается, исправляйте код, даже если валидатор зелен.
Работает ли ARIA во всех браузерах одинаково?
Поддержка базовых атрибутов отличная во всех современных браузерах (Chrome, Firefox, Safari, Edge). Однако реализация может отличаться в зависимости от комбинации «браузер + скринридер». Например, поведение aria-live зон может немного варьироваться в Safari на iOS по сравнению с Android. Всегда тестируйте на целевых платформах ваших пользователей.
Что делать, если у меня сложный компонент, который нельзя описать стандартными ролями?
Попробуйте разбить его на простые части или использовать паттерны из WAI-ARIA Authoring Practices Guide (APG). Там есть готовые примеры разметки для почти всех типовых компонентов (табы, комбобоксы, деревья). Если компонент уникален, рассмотрите возможность упрощения дизайна до уровня, который поддерживается существующими ролями, так как поддержка новых экспериментальных ролей скринридерами может занимать годы.