Что такое ARIA-атрибуты и почему они важны для доступности (accessibility)?
ARIA-атрибуты расширяют дерево доступности браузера ролями, состояниями и свойствами — вспомогательные технологии (screen readers) читают именно его. Первое правило ARIA: используй нативный HTML-элемент там, где он есть.
Что такое дерево доступности
Браузер строит два дерева из HTML: DOM-дерево (для рендеринга) и дерево доступности (Accessibility Tree) — для вспомогательных технологий: скринридеров, программ управления голосом, брайлевских дисплеев. Каждый узел дерева доступности содержит роль, имя, состояние и свойства. ARIA (Accessible Rich Internet Applications) — это набор атрибутов, которые позволяют управлять этим деревом напрямую, когда нативной семантики HTML недостаточно.
Три группы ARIA-атрибутов
- Роли (
role) — что это за элемент:role="dialog",role="tab",role="alert". Перекрывают нативную семантику тега. - Состояния — динамически меняются в зависимости от UI:
aria-expanded,aria-checked,aria-disabled,aria-pressed. - Свойства — описывают связи и метаданные:
aria-label,aria-labelledby,aria-describedby,aria-controls,aria-live.
Первое правило ARIA
Если существует нативный HTML-элемент или атрибут с нужной семантикой — используй его, а не ARIA. <button> уже несёт роль button, фокусируется с клавиатуры и реагирует на Enter/Space. ARIA это не заменит — она только объявляет роль скринридеру, но не добавляет клавиатурное поведение автоматически.
Практический пример: аккордеон
<!-- Кнопка-триггер -->
<button
id="faq-trigger-1"
aria-expanded="false"
aria-controls="faq-panel-1"
>
Как оформить возврат?
</button>
<!-- Панель -->
<div
id="faq-panel-1"
role="region"
aria-labelledby="faq-trigger-1"
hidden
>
<p>Заполните форму на сайте в течение 14 дней...</p>
</div>
const trigger = document.getElementById('faq-trigger-1');
const panel = document.getElementById('faq-panel-1');
trigger.addEventListener('click', () => {
const expanded = trigger.getAttribute('aria-expanded') === 'true';
trigger.setAttribute('aria-expanded', String(!expanded));
panel.hidden = expanded; // toggle видимость
});
Скринридер объявит: «Как оформить возврат? Кнопка, свёрнуто». После клика — «развёрнуто». Без aria-expanded пользователь не узнает, что панель открылась.
Live-регионы: динамический контент
<!-- Скринридер озвучит содержимое автоматически при изменении -->
<div
role="status"
aria-live="polite"
aria-atomic="true"
></div>
async function submitForm(data) {
const status = document.querySelector('[role="status"]');
try {
await api.post('/orders', data);
status.textContent = 'Заказ оформлен. Номер: #4821';
} catch {
status.textContent = 'Ошибка при оформлении. Попробуйте снова.';
}
}
aria-live="polite" — скринридер дочитает текущее предложение и только потом огласит обновление. aria-live="assertive" — перебивает немедленно (только для критических ошибок).
Именование элементов: aria-label vs aria-labelledby
<!-- Иконочная кнопка без видимого текста -->
<button aria-label="Закрыть диалог">
<svg aria-hidden="true">...</svg>
</button>
<!-- Поле, связанное с видимым заголовком -->
<h2 id="shipping-title">Адрес доставки</h2>
<input aria-labelledby="shipping-title" type="text" />
aria-hidden="true" на SVG убирает его из дерева доступности — иначе скринридер попытается прочитать пути SVG как текст.
Подводные камни
- ARIA не добавляет поведение.
role="button"на<div>объявит роль скринридеру, но div не фокусируется Tab и не реагирует на Enter без явногоtabindex="0"и обработчикаkeydown. Используй<button>. - Несинхронное состояние. Открыл панель, но забыл переключить
aria-expanded— скринридер говорит «свёрнуто», хотя контент виден. Состояния ARIA нужно обновлять в том же обработчике, что и визуальные изменения. - Дублирование имён. Кнопка с текстом «Удалить» и
aria-label="Удалить товар из корзины»— нормально. Кнопка с текстом «Удалить товар из корзины» и тем жеaria-label— скринридер прочитает label, проигнорирует текст. Следи, чтобы видимый текст и aria-имя не противоречили друг другу. - Избыточный aria-hidden.
aria-hidden="true"на контейнере, внутри которого есть фокусируемые элементы — создаёт «невидимые» кнопки, на которые попадает Tab-фокус, но скринридер не объявляет их содержимое. - role="presentation" не то же самое, что aria-hidden.
role="presentation"убирает семантику элемента, но дочерние элементы остаются в дереве. Это не «скрыть» элемент. - Живые регионы — только пустые при инициализации. Если
aria-live-контейнер уже содержит текст при загрузке страницы, скринридеры не объявят его — только последующие изменения. - Поддержка комбинаций browser+AT неоднородна.
aria-expandedна<select>игнорируется в большинстве скринридеров. Перед внедрением нестандартного паттерна проверяй матрицу поддержки на a11ysupport.io. - Не злоупотребляй role="alert". Alert — assertive live region. Несколько одновременных alert-элементов на странице (ошибки валидации формы) создадут хаос — скринридер будет перебивать себя. Используй одну область с
aria-live="polite"или суммарное сообщение.
Common mistakes
- Смешивать «ARIA-атрибуты» с похожим механизмом без критерия выбора.
- Игнорировать риск: неверно оценить границы применения темы «ARIA-атрибуты» и получить хрупкое решение.
- Показывать только синтаксис и не объяснять поведение в runtime или сборке.
What the interviewer is testing
- Объясняет доступность интерактивных интерфейсов, когда нативной семантики недостаточно.
- Показывает на примере, как работает: ARIA дополняет дерево доступности ролями, состояниями и свойствами, но не добавляет клавиатурное поведение и не исправляет неверный выбор HTML-элемента автоматически.
- Называет production-нюанс и граничный случай для темы «ARIA-атрибуты».