ARIA атрибути для розробників — практичний гайд
ARIA (Accessible Rich Internet Applications) — набір атрибутів HTML, що дозволяють передавати семантику та стан інтерактивних компонентів до допоміжних технологій (скрін-рідерів, брайлівських дисплеїв). Специфікація розроблена W3C WAI і є частиною стандарту WCAG.
Коротко: ARIA дозволяє скрін-рідеру "зрозуміти" кастомні компоненти — випадаючі меню, вкладки, accordion, modal-вікна — і правильно їх озвучити.
Факт: Неправильне використання ARIA робить сайт гіршим, ніж без ARIA взагалі. За даними WebAIM Screen Reader Survey, головна скарга — компоненти з помилковою ARIA-семантикою.
Що таке ARIA і навіщо?
ARIA додає три речі до HTML-елементів:
- Ролі (roles) — що це за елемент? (
button,dialog,tabpanel) - Властивості (properties) — постійні характеристики (
aria-label,aria-required) - Стани (states) — динамічні характеристики (
aria-expanded,aria-checked,aria-disabled)
Без ARIA скрін-рідер бачить лише голий HTML. З ARIA — він розуміє, що <div class="dropdown"> — це насправді combobox, який зараз розгорнутий (aria-expanded="true") і показує список з 5 опцій (aria-activedescendant).
Перше правило ARIA (не використовуй якщо можна без)
Офіційне перше правило ARIA від W3C:
"Якщо ви можете використати нативний HTML-елемент або атрибут з потрібною семантикою та поведінкою, замість того щоб перевизначати елемент і додавати ARIA роль, стан або властивість — зробіть це."
<!-- ❌ Погано — ARIA де нативний HTML кращий -->
<div role="button" tabindex="0" onclick="submit()">Надіслати</div>
<div role="checkbox" aria-checked="true">Погоджуюсь</div>
<!-- ✅ Добре — нативний HTML -->
<button type="submit">Надіслати</button>
<input type="checkbox" checked id="agree" />
<label for="agree">Погоджуюсь</label>
Нативні HTML-елементи вже мають правильні ролі, стани, фокус і обробку клавіатури. <button> відповідає на Enter і Space, <input type="checkbox"> — на Space, <a> — на Enter. З <div role="button"> вам потрібно реалізовувати все це вручну.
aria-label, aria-labelledby, aria-describedby
Ці три атрибути відповідають на різні питання скрін-рідера:
aria-label — "як це називається?"
Додає текстовий підпис безпосередньо в атрибут. Перевизначає видимий текст елемента.
<!-- Кнопка без видимого тексту (лише іконка) -->
<button aria-label="Закрити діалогове вікно">
<svg aria-hidden="true" focusable="false">
<!-- X icon -->
</svg>
</button>
<!-- Поле пошуку -->
<input type="search" aria-label="Пошук по сайту" placeholder="Введіть запит" />
<!-- Landmark з уточненням -->
<nav aria-label="Головна навігація">...</nav>
<nav aria-label="Навігація у підвалі">...</nav>
Коли використовувати: для елементів без видимого тексту (іконки-кнопки) або коли видимий текст недостатньо описовий.
aria-labelledby — "вказати на видимий заголовок"
Посилається на інший елемент на сторінці як підпис. Перевага: підпис видимий всім користувачам.
<!-- Модальне вікно з заголовком -->
<div role="dialog" aria-labelledby="modal-title" aria-modal="true">
<h2 id="modal-title">Підтвердження видалення</h2>
<p>Ви впевнені, що хочете видалити цей файл?</p>
<button>Видалити</button>
<button>Скасувати</button>
</div>
<!-- Секція зі заголовком -->
<section aria-labelledby="news-heading">
<h2 id="news-heading">Останні новини</h2>
...
</section>
aria-describedby — "додаткова інформація"
Вказує на елемент, що надає додаткове пояснення (на відміну від підпису).
<!-- Поле з підказкою -->
<label for="password">Пароль</label>
<input type="password" id="password" aria-describedby="password-hint" />
<p id="password-hint">Мінімум 8 символів, одна велика літера і цифра</p>
<!-- Поле з помилкою -->
<label for="email">Email</label>
<input
type="email"
id="email"
aria-invalid="true"
aria-describedby="email-error"
/>
<p id="email-error" role="alert">Введіть коректну email-адресу</p>
role атрибути
role повідомляє скрін-рідеру, яку функцію виконує елемент.
Найважливіші ролі:
<!-- Навігаційна структура -->
<div role="banner">Шапка сайту</div>
<!-- = <header> -->
<div role="navigation">Меню</div>
<!-- = <nav> -->
<div role="main">Основний контент</div>
<!-- = <main> -->
<div role="contentinfo">Підвал</div>
<!-- = <footer> -->
<div role="complementary">Сайдбар</div>
<!-- = <aside> -->
<!-- Інтерактивні компоненти -->
<div role="dialog" aria-modal="true">Модальне вікно</div>
<div role="alert">Повідомлення про помилку</div>
<div role="status">Успішно збережено</div>
<!-- Складні компоненти -->
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel1">
Вкладка 1
</button>
<button role="tab" aria-selected="false" aria-controls="panel2">
Вкладка 2
</button>
</div>
<div role="tabpanel" id="panel1">Вміст вкладки 1</div>
Важливо: role="presentation" або role="none" прибирає семантику елемента (для декоративних таблиць, наприклад):
<table role="presentation">
<!-- Таблиця для верстки, не для даних — прибираємо семантику таблиці -->
</table>
aria-live для динамічного контенту
Коли контент змінюється без перезавантаження сторінки, скрін-рідер не знає про це. aria-live робить регіон "живим" — зміни оголошуються автоматично.
<!-- Повідомлення про успіх/помилку після відправки форми -->
<div aria-live="polite" aria-atomic="true" id="form-status">
<!-- JS оновлює цей контент: -->
<!-- "Форму успішно відправлено" -->
</div>
<!-- Лічильник символів -->
<textarea id="message" maxlength="280"></textarea>
<p aria-live="polite"><span id="char-count">0</span>/280 символів</p>
<!-- Критичні повідомлення (переривають поточне читання) -->
<div aria-live="assertive" role="alert">
Сеанс закінчується через 2 хвилини. Збережіть дані.
</div>
Значення aria-live:
polite— оголошується після завершення поточного читання (для більшості оновлень)assertive— переривається і оголошується негайно (лише для критичних сповіщень)off— вимкнено (за замовчуванням)
aria-atomic="true" — весь регіон читається заново при будь-якій зміні (рекомендовано для статус-повідомлень).
aria-expanded, aria-controls для інтерактивних елементів
Accordion / Dropdown
<!-- Accordion -->
<button
aria-expanded="false"
aria-controls="section-content"
id="section-toggle"
>
Розкрийте розділ
</button>
<div id="section-content" hidden>
<p>Вміст розділу...</p>
</div>
// JS для перемикання
const btn = document.getElementById("section-toggle");
const content = document.getElementById("section-content");
btn.addEventListener("click", () => {
const isExpanded = btn.getAttribute("aria-expanded") === "true";
btn.setAttribute("aria-expanded", !isExpanded);
content.hidden = isExpanded;
});
Навігаційне меню з підменю
<nav aria-label="Головна навігація">
<ul>
<li>
<button
aria-expanded="false"
aria-haspopup="true"
aria-controls="products-menu"
>
Продукти
</button>
<ul id="products-menu" hidden role="menu">
<li role="menuitem"><a href="/audit">Аудит</a></li>
<li role="menuitem"><a href="/rankings">Рейтинги</a></li>
</ul>
</li>
</ul>
</nav>
Модальне вікно (повний приклад)
<!-- Trigger -->
<button aria-haspopup="dialog" onclick="openModal()">Відкрити деталі</button>
<!-- Modal -->
<div
id="modal"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-desc"
hidden
>
<h2 id="modal-title">Заголовок діалогу</h2>
<p id="modal-desc">Опис дії або змісту діалогу.</p>
<button>Підтвердити</button>
<button onclick="closeModal()">Скасувати</button>
</div>
function openModal() {
const modal = document.getElementById("modal");
modal.hidden = false;
// Перемістити фокус всередину модалки
modal.querySelector("button").focus();
// Заблокувати скрол фону
document.body.style.overflow = "hidden";
// Escape закриває
document.addEventListener("keydown", handleEscape);
}
function handleEscape(e) {
if (e.key === "Escape") closeModal();
}
Поширені питання про ARIA
Чи замінює ARIA семантичний HTML?
Ні. ARIA доповнює HTML там, де нативних елементів недостатньо. Завжди надавайте перевагу нативному HTML — <button>, <input>, <select>, <nav>, <main>.
Що буде якщо я неправильно використаю ARIA?
Скрін-рідер може озвучити неправильну роль або стан. Наприклад, role="button" на <div> без tabindex="0" і обробника клавіатури — недоступний. Неправильний aria-expanded — користувач не знає, відкрито підменю чи ні.
Скільки ARIA атрибутів потрібно для типового сайту?
Для більшості сайтів достатньо: aria-label для іконок, aria-expanded для меню/accordion, aria-live для динамічних оновлень, aria-hidden для декоративних елементів. Решта — для складних кастомних компонентів.