Реализация ARIA-атрибутов для доступности сайта
ARIA (Accessible Rich Internet Applications) — набор HTML-атрибутов, расширяющих семантику элементов для вспомогательных технологий. Используется когда нативной HTML-семантики недостаточно — главным образом для кастомных интерактивных компонентов.
Первое правило ARIA
Не использовать ARIA, если можно обойтись нативным HTML. Нативные элементы уже имеют встроенную доступность:
<!-- Плохо — ARIA-костыли на div -->
<div role="button" tabindex="0" aria-pressed="false">Переключить</div>
<!-- Хорошо — нативный button -->
<button type="button">Переключить</button>
<!-- Плохо -->
<div role="heading" aria-level="2">Заголовок</div>
<!-- Хорошо -->
<h2>Заголовок</h2>
Ключевые ARIA-атрибуты
Роли (roles):
<div role="alert">Ошибка при сохранении</div> <!-- важное сообщение -->
<div role="status">Сохранено успешно</div> <!-- некритичное уведомление -->
<nav role="navigation" aria-label="Основная">...</nav> <!-- навигационный блок -->
<div role="dialog" aria-modal="true">...</div> <!-- модальное окно -->
<ul role="listbox">...</ul> <!-- список выбора -->
Состояния:
<button aria-expanded="false" aria-controls="dropdown-menu">Меню</button>
<ul id="dropdown-menu" aria-hidden="true">...</ul>
<input type="checkbox" aria-checked="mixed"> <!-- промежуточное состояние -->
<button aria-pressed="true">Жирный</button> <!-- toggle-кнопка -->
<input aria-disabled="true"> <!-- визуально отключено -->
<div aria-busy="true">Загрузка...</div>
Связи между элементами:
<!-- aria-labelledby — заголовок элемента -->
<section aria-labelledby="section-title">
<h2 id="section-title">О компании</h2>
</section>
<!-- aria-describedby — дополнительное описание -->
<input id="password" type="password"
aria-describedby="password-requirements">
<p id="password-requirements">Минимум 8 символов, одна цифра</p>
<!-- aria-controls — управляет другим элементом -->
<button aria-controls="panel-1" aria-expanded="false">Показать детали</button>
<!-- aria-owns — элементы, не являющиеся потомками в DOM -->
<ul role="listbox" aria-owns="option-1 option-2">...</ul>
Живые регионы (Live Regions)
<!-- Автоматически объявляется screen reader при изменении -->
<div aria-live="polite" aria-atomic="true">
<!-- Вежливо: объявить после текущей речи -->
Корзина обновлена: 3 товара
</div>
<div aria-live="assertive">
<!-- Немедленно прервать текущую речь — только для критических ошибок -->
Ошибка: не удалось подключиться к серверу
</div>
<!-- role="status" = aria-live="polite" + aria-atomic="true" -->
<p role="status">Изменения сохранены</p>
<!-- role="alert" = aria-live="assertive" -->
<p role="alert">Форма содержит ошибки</p>
Компонент уведомлений с ARIA
function NotificationSystem() {
const [notifications, setNotifications] = useState<Notification[]>([]);
return (
<>
{/* Регион для screen reader — всегда в DOM */}
<div
aria-live="polite"
aria-atomic="false"
className="sr-only"
id="sr-notifications"
>
{notifications.map(n => (
<p key={n.id}>{n.message}</p>
))}
</div>
{/* Визуальные уведомления */}
<div className="toast-container">
{notifications.map(n => (
<Toast key={n.id} notification={n} />
))}
</div>
</>
);
}
Аккордеон
function Accordion({ items }) {
const [openIndex, setOpenIndex] = useState<number | null>(null);
return (
<div>
{items.map((item, i) => (
<div key={item.id}>
<h3>
<button
aria-expanded={openIndex === i}
aria-controls={`panel-${i}`}
id={`header-${i}`}
onClick={() => setOpenIndex(openIndex === i ? null : i)}
>
{item.title}
</button>
</h3>
<div
id={`panel-${i}`}
role="region"
aria-labelledby={`header-${i}`}
hidden={openIndex !== i}
>
{item.content}
</div>
</div>
))}
</div>
);
}
Таблицы данных
<table>
<caption>Статистика продаж за Q3 2024</caption>
<thead>
<tr>
<th scope="col">Продукт</th>
<th scope="col">Продажи</th>
<th scope="col">Изменение</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Продукт A</th>
<td>1 240</td>
<td>
<span aria-label="рост на 12%">+12%</span>
</td>
</tr>
</tbody>
</table>
Частые ошибки
-
aria-hidden="true"на интерактивных элементах — они становятся недоступны с клавиатуры - Дублирование нативной семантики:
<button role="button">— избыточно -
aria-labelна несемантических div без role — не работает - Динамическое обновление
aria-labelбезaria-live— screen reader не заметит
Срок реализации
Добавление ARIA на кастомные компоненты в существующем проекте: 2–4 дня в зависимости от количества компонентов.







