ARIA и динамический контент: когда атрибуты помогают, а когда вредят
Первое правило ARIA, типичные ошибки, состояния элементов и live-регионы для уведомлений, загрузки и одностраничных приложений

Часть 6 из 8 серии «Доступный веб на практике». Предыдущая часть: Контраст, анимация и размер кнопок: визуальная доступность сайта.
ARIA-атрибутов на сайтах становится всё больше, а ошибок вместе с ними — тоже. В этой части разберёмся, когда ARIA действительно нужна, как её не сломать и как сообщать скринридеру об изменениях на странице.
ARIA: когда нужна и когда вредит
WAI-ARIA — набор атрибутов (role, aria-*), которые изменяют то, как элемент представлен в дереве доступности. ARIA ничего не меняет в поведении: role="button" не сделает div фокусируемым и не добавит реакцию на клавиши.
Первое правило ARIA
Если можно использовать нативный HTML-элемент или атрибут с нужной семантикой и поведением — используйте его вместо того, чтобы переназначать элемент и добавлять ARIA. — W3C, Using ARIA
У этого правила есть статистика. В отчёте WebAIM Million 2026 страницы с ARIA содержат в среднем 59,1 ошибки, без ARIA — 42, а среднее число ARIA-атрибутов на страницу превысило 133 и выросло на 27% за год. Это не значит, что ARIA вызывает ошибки сама по себе: она чаще встречается на сложных страницах. Но неправильная ARIA делает хуже, чем её отсутствие, — неверная роль или устаревшее состояние прямо дезинформируют пользователя.
Частые ошибки:
<!-- ❌ aria-label на элементе без роли игнорируется или читается непредсказуемо -->
<div aria-label="Карточка товара">...</div>
<!-- ❌ Дублирование: скринридер прочитает «кнопка» и так -->
<button role="button">OK</button>
<!-- ❌ aria-hidden на фокусируемом элементе: фокус попадает «в никуда» -->
<a href="/cart" aria-hidden="true">Корзина</a>
<!-- ❌ aria-label расходится с видимым текстом — ломает голосовое управление -->
<button aria-label="submit">Отправить заявку</button>
Последний пример нарушает критерий 2.5.3 Label in Name: доступное имя должно содержать видимый текст, желательно начинаться с него.
Где ARIA действительно нужна
ARIA незаменима там, где в HTML нет подходящего элемента или нужно передать состояние:
Раскрывающийся блок (disclosure)
<button type="button" aria-expanded="false" aria-controls="faq-1">
Сколько стоит сайт?
</button>
<div id="faq-1" hidden>
<p>Лендинг — от ... Многостраничный сайт — от ...</p>
</div>
document.querySelectorAll('[aria-controls]').forEach((button) => {
const panel = document.getElementById(button.getAttribute('aria-controls'));
button.addEventListener('click', () => {
const expanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!expanded));
panel.hidden = expanded;
});
});
Для простых случаев есть нативный вариант без JavaScript — <details> и <summary>:
<details>
<summary>Сколько стоит сайт?</summary>
<p>Лендинг — от ... Многостраничный сайт — от ...</p>
</details>
Кнопка-переключатель
<button type="button" aria-pressed="false">Только избранное</button>
Подписи и описания
aria-labelledby— имя берётся из другого видимого элемента (заголовок модалки, заголовок секции);aria-describedby— дополнительное описание (подсказка, ошибка);aria-label— имя, когда видимого текста нет (кнопка-иконка).
Приоритет: видимый текст внутри элемента → aria-labelledby → aria-label. Имена, которые видит пользователь, всегда лучше невидимых.
Состояния: aria-expanded, aria-pressed, aria-selected, aria-checked, aria-current, aria-invalid, aria-busy, aria-disabled. Главное — обновлять их синхронно с визуальным состоянием. Меню открыто, а aria-expanded="false" — это хуже, чем вообще без атрибута.
Для сложных виджетов (вкладки, комбобокс, меню, дерево, грид) — ARIA Authoring Practices Guide. Но учтите предупреждение самих авторов: паттерны APG — это демонстрация, а не готовые к продакшену компоненты, и поддержка во вспомогательных технологиях проверяется отдельно.
Динамический контент и объявления
В одностраничных приложениях содержимое меняется без перезагрузки: товар добавился в корзину, появилось уведомление, загрузились результаты поиска. Зрячий пользователь это видит, а скринридер — молчит, если ему не сказать.
Live-регионы
Класс visually-hidden в примерах ниже скрывает элемент визуально, но оставляет его скринридерам. Его CSS — в части «Семантический HTML и альтернативный текст».
<!-- Регион должен существовать в DOM ДО изменения текста -->
<div id="status" role="status" aria-live="polite" class="visually-hidden"></div>
function announce(message) {
const region = document.getElementById('status');
region.textContent = ''; // сброс, чтобы повтор того же текста тоже прочитался
setTimeout(() => { region.textContent = message; }, 100);
}
addToCartButton.addEventListener('click', async () => {
await addToCart(productId);
announce('Товар добавлен в корзину. В корзине 3 товара.');
});
| Атрибут | Поведение | Когда |
|---|---|---|
aria-live="polite" / role="status" |
Прочитает, когда скринридер закончит текущую фразу | Статусы, уведомления, «найдено 12 результатов» |
aria-live="assertive" / role="alert" |
Прервёт и прочитает сразу | Только срочное: ошибка, сессия истекает |
Самая частая ошибка — создать регион вместе с сообщением (например, отрендерить тост с role="alert" в момент события). Многие скринридеры отслеживают изменения только в регионах, которые уже были на странице. Держите один-два пустых региона в разметке с самого начала и меняйте их текст.
Не злоупотребляйте assertive: постоянные прерывания мешают так же, как всплывающие окна.
Загрузка
<section aria-labelledby="results-title" aria-busy="true">
<h2 id="results-title">Результаты поиска</h2>
<!-- скелетон -->
</section>
aria-busy="true" подсказывает, что содержимое обновляется. После загрузки снимите его и объявите результат через live-регион: «Найдено 12 вакансий».
Тосты и уведомления
Если уведомление исчезает само, у человека должно хватить времени его прочитать (критерий 2.2.1), а если в нём есть действие («Отменить»), до него нужно иметь возможность добраться клавиатурой. Надёжное правило: уведомления с действиями не исчезают автоматически, а важные действия дублируются вне тоста.
Бесконечная прокрутка
Бесконечная лента недоступна до футера для клавиатуры и тяжела для скринридера. Кнопка «Показать ещё» решает обе проблемы и даёт пользователю контроль. После нажатия переведите фокус на первый новый элемент.
Серия «Доступный веб на практике»
- Доступность сайта: для кого она на самом деле и с чего начать
- Семантический HTML и альтернативный текст: фундамент доступного сайта
- Доступные формы: подписи, подсказки, ошибки и автозаполнение
- Клавиатура и фокус: как сделать сайт, которым можно пользоваться без мыши
- Контраст, анимация и размер кнопок: визуальная доступность сайта
- ARIA и динамический контент: когда атрибуты помогают, а когда вредят — вы здесь
- Доступность в React: id, фокус при смене маршрута и объявления
- Как тестировать доступность: автоматика, ручная проверка и чеклист
Следующая часть: Доступность в React: id, фокус при смене маршрута и объявления
Agama Labs — разработка сайтов и веб-приложений. Если хотите проверить доступность своего сайта, напишите нам: hello@agamalabs.com


