Олег Марков
ARIA роль - полное руководство для разработчиков
Введение
ARIA-роли — это способ объяснить вспомогательным технологиям (например, скринридерам), что именно находится на странице и как с этим можно взаимодействовать. Браузеру обычно достаточно HTML, чтобы отрисовать интерфейс, но скринридеру нужно больше семантики, чем просто набор <div> и <span>. Именно здесь вступают в игру ARIA-атрибуты и в первую очередь role.
Смотрите, здесь важно понимать простую мысль: HTML дает семантику «по умолчанию» (заголовок, кнопка, ссылка, список), а ARIA позволяет:
- уточнить или дополнить семантику,
- исправить ее, если разметка сделана нестандартным способом,
- создать доступный кастомный компонент (например, виджет на чистом JavaScript).
Давайте разберемся, как правильно использовать ARIA-роль, какие бывают типы ролей, чем отличаются явные и неявные роли, и в каких случаях role действительно нужен, а в каких — только мешает.
Что такое ARIA-роль
Зачем вообще нужен атрибут role
Атрибут role описывает тип элемента с точки зрения доступности. Скринридер, встретив элемент с ролью, воспринимает его как определенный вид интерфейсного компонента: кнопку, диалог, вкладку, навигационный блок и т. д.
Например:
<div role="button" tabindex="0">
Открыть меню
</div>Комментарии:
role="button"сообщает скринридеру, что это кнопка.tabindex="0"делает элемент фокусируемым с клавиатуры, иначе пользователь не сможет до него добраться без мыши.
Как видите, здесь обычный <div> получает семантику кнопки через ARIA. Но важно понимать: вы берете на себя ответственность полностью имитировать поведение настоящей кнопки — и по клавиатуре, и по состояниям.
Связь HTML и ARIA
Многие HTML-элементы уже имеют «встроенную» роль:
<button>→ роль кнопки,<a href="…">→ роль ссылки,<ul>/<ol>→ роль списка,<nav>→ роль навигации,<header>/<footer>(при определенных условиях) → роль баннера или содержательного блока.
Эта роль называется неявной (implicit). Вы ее не пишете, но она существует.
Если вы явно укажете role на элементе с уже существующей неявной ролью, то:
- или перепишете ее,
- или вообще ничего не поменяете (если браузер игнорирует конфликтующую роль).
Поэтому хорошее базовое правило:
Сначала используйте правильные HTML-элементы. ARIA-роль — только там, где стандартных элементов не хватает.
Категории ARIA-ролей
Спецификация разделяет роли на группы. Сейчас мы не будем глубоко уходить в теорию WAI-ARIA, но краткое понимание групп поможет ориентироваться.
Семантические (структурные) роли
Эти роли описывают крупные блоки интерфейса:
banner— верхний баннер/шапка сайта,navigation— блок навигации,main— основное содержимое страницы,contentinfo— нижний колонтитул (часто — подвал сайта),complementary— дополнительный контент (сайдбары, боковые панели).
Пример:
<header role="banner">
<!-- Здесь шапка сайта -->
</header>
<nav role="navigation">
<!-- Главное меню -->
</nav>
<main role="main">
<!-- Основной контент -->
</main>
<footer role="contentinfo">
<!-- Футер -->
</footer>На практике во многих случаях роли здесь можно не указывать, так как современные браузеры и скринридеры уже понимают семантические теги. Но примеры помогут вам связать роли с реальными блоками страницы.
Взаимодействующие роли (widgets)
Это роли интерактивных элементов:
button— кнопка,link— ссылка,checkbox— чекбокс,radio— радиокнопка,switch— переключатель,tab,tablist,tabpanel— вкладки,dialog— диалоговое окно,alertdialog— важный диалог с акцентом на внимание,slider— ползунок,menu,menuitem,menubar,menuitemcheckbox,menuitemradio— элементы меню,- и другие.
Как правило, такие роли вы будете использовать при создании кастомных компонентов. Например, если нужно сделать собственный переключатель, который выглядит иначе, чем стандартный <input type="checkbox">.
Документные и абстрактные роли
Есть роли, которые используются в сложных документах, приложениях, таблицах, деревьях (article, document, grid, row, cell, treeitem и др.), а также абстрактные роли, которые напрямую не задаются в HTML. Для начала можно про них помнить только одно: не все роли предназначены для прямого использования в коде.
Неявные и явные роли
Неявная (implicit) роль
Каждый семантический HTML-элемент обладает базовой ролью по умолчанию. Давайте посмотрим на несколько примеров.
<button>Сохранить</button>
<a href="/profile">Профиль</a>
<h1>Заголовок страницы</h1>
<ul>
<li>Пункт 1</li>
<li>Пункт 2</li>
</ul>Неявные роли здесь:
<button>→button,<a href>→link,<h1>→heading(с уровнем заголовка),<ul>→list,<li>→listitem.
Когда вы используете семантический элемент, вы как бы автоматически получаете ARIA-роль, даже не прописывая role.
Явная (explicit) роль
Явная роль — это когда вы пишете role="..." в атрибуте:
<div role="navigation">
<!-- Меню -->
</div>
<span role="heading" aria-level="2">
Заголовок блока
</span>В этих примерах:
<div>превращается в навигационный блок для скринридера,<span>— в заголовок второго уровня.
Такой подход нужен, когда:
- нет возможности использовать подходящий HTML-тег,
- HTML-тег есть, но он не дает нужную роль,
- вы пишете переиспользуемый виджет, у которого могут быть разные оболочки.
Что происходит при конфликте ролей
Если элемент уже имеет неявную роль, а вы добавили ему явную, то:
- в ряде случаев явная роль переписывает неявную,
- иногда явная роль проигнорируется, если спецификация считает такую комбинацию недопустимой.
Например:
<button role="link">Профиль</button>Формально вы заявляете, что эта кнопка — ссылка. Для пользователя это может быть запутанно: он ожидает кнопку, а скринридер объявляет ссылку. Такие вещи лучше не делать. Вместо этого используйте <a> с правильными стилями.
Основные практические сценарии использования ARIA-ролей
Кастомные кнопки на диве или спане
Иногда дизайнеры просят нестандартную кнопку, и разработчики делают ее на <div>. Покажу вам, как это реализовано на практике с точки зрения доступности.
Пример без ARIA (плохо)
<div class="btn-primary" onclick="submitForm()">
Отправить
</div>Проблемы:
- элемент нельзя сфокусировать клавиатурой,
- скринридер не поймет, что это кнопка,
- активация только мышью, нет реакции на Enter/Space.
Пример с ARIA-ролью и клавиатурой (лучше)
<div
role="button"
tabindex="0"
class="btn-primary"
onclick="submitForm()"
onkeydown="handleKey(event)"
>
Отправить
</div>
<script>
// Обрабатываем нажатия клавиш для имитации поведения кнопки
function handleKey(event) {
// Здесь мы реагируем на Enter и пробел
if (event.key === "Enter" || event.key === " ") {
event.preventDefault() // Не даем странице прокрутиться при пробеле
submitForm()
}
}
function submitForm() {
// Логика отправки формы
console.log("Форма отправлена")
}
</script>Что здесь происходит:
role="button"— объявляем, что элемент ведет себя как кнопка.tabindex="0"— добавляем элемент в порядок табуляции.- Обрабатываем клавиши Enter и пробел так, как это делает нативная кнопка.
Но при этом важно: если можно использовать <button> и просто стилизовать его под нужный вид — так и делайте. Это будет проще и надежнее.
Кастомное диалоговое окно (role="dialog")
Модальные окна — частый пример, где используется ARIA. Давайте разберем упрощенный вариант.
<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 type="button" onclick="confirmDelete()">Удалить</button>
<button type="button" onclick="closeModal()">Отмена</button>
</div>
<button type="button" onclick="openModal()" id="open-btn">
Открыть модальное окно
</button>
<script>
let lastFocusedElement = null
function openModal() {
// Здесь мы запоминаем элемент, который был в фокусе до открытия диалога
lastFocusedElement = document.activeElement
const modal = document.getElementById("modal")
modal.hidden = false
// Переводим фокус внутрь диалога
const title = document.getElementById("modal-title")
title.focus()
}
function closeModal() {
const modal = document.getElementById("modal")
modal.hidden = true
// Возвращаем фокус тому, кто открыл диалог
if (lastFocusedElement) {
lastFocusedElement.focus()
}
}
function confirmDelete() {
console.log("Файл удален")
closeModal()
}
</script>Ключевые моменты:
role="dialog"— сообщает, что это диалоговое окно.aria-modal="true"— дает понять, что остальной контент временно не доступен для взаимодействия.aria-labelledbyиaria-describedby— связывают заголовок и текст с диалогом.- Управление фокусом: при открытии переносим его в диалог, при закрытии возвращаем назад.
На практике к этому нужно добавить «ловушку фокуса» внутри диалога и блокировку фона для скринридера, но уже сейчас вы видите базовый каркас использования роли.
Роль navigation для меню
Семантический тег <nav> уже воспринимается как навигация, поэтому role="navigation" часто не требуется. Но бывает, что навигация построена на <div>:
<div role="navigation" aria-label="Основное меню">
<a href="/news">Новости</a>
<a href="/blog">Блог</a>
<a href="/contacts">Контакты</a>
</div>Здесь:
role="navigation"— блок объявляется навигационным.aria-label="Основное меню"— дает название области, которое скринридер покажет пользователю.
Если вы используете <nav>, то лучше написать:
<nav aria-label="Основное меню">
<a href="/news">Новости</a>
<a href="/blog">Блог</a>
<a href="/contacts">Контакты</a>
</nav>В этом случае дополнительная ARIA-роль не нужна — HTML уже дает ее по умолчанию.
Таб-компонент (вкладки) с role="tablist", tab, tabpanel
Вкладки — классический пример кастомного компонента, где ARIA-ролям есть чем заняться.
Давайте посмотрим, что происходит в следующем примере.
<div role="tablist" aria-label="Настройки профиля">
<button
role="tab"
aria-selected="true"
aria-controls="panel-general"
id="tab-general"
>
Общие
</button>
<button
role="tab"
aria-selected="false"
aria-controls="panel-security"
id="tab-security"
tabindex="-1"
>
Безопасность
</button>
</div>
<div
role="tabpanel"
id="panel-general"
aria-labelledby="tab-general"
>
<!-- Контент вкладки Общие -->
<p>Здесь общие настройки профиля.</p>
</div>
<div
role="tabpanel"
id="panel-security"
aria-labelledby="tab-security"
hidden
>
<!-- Контент вкладки Безопасность -->
<p>Здесь настройки безопасности.</p>
</div>
<script>
// Здесь мы находим все вкладки и панели
const tabs = document.querySelectorAll('[role="tab"]')
const tabPanels = document.querySelectorAll('[role="tabpanel"]')
// Функция переключения вкладок
function switchTab(newTab) {
// Отключаем текущую активную вкладку
tabs.forEach(tab => {
const isSelected = tab === newTab
tab.setAttribute("aria-selected", isSelected ? "true" : "false")
tab.setAttribute("tabindex", isSelected ? "0" : "-1")
})
// Показываем только активную панель
tabPanels.forEach(panel => {
const controlsId = newTab.getAttribute("aria-controls")
const isActive = panel.id === controlsId
panel.hidden = !isActive
})
// Переносим фокус на новую вкладку
newTab.focus()
}
// Подключаем обработчики событий
tabs.forEach(tab => {
tab.addEventListener("click", () => {
switchTab(tab)
})
tab.addEventListener("keydown", event => {
// Здесь мы реализуем навигацию по вкладкам с помощью стрелок
let newIndex
const currentIndex = Array.from(tabs).indexOf(tab)
if (event.key === "ArrowRight") {
newIndex = (currentIndex + 1) % tabs.length
} else if (event.key === "ArrowLeft") {
newIndex = (currentIndex - 1 + tabs.length) % tabs.length
} else {
return
}
event.preventDefault()
switchTab(tabs[newIndex])
})
})
</script>Здесь вы видите типичный паттерн:
role="tablist"на контейнере с вкладками,role="tab"на самих вкладках,role="tabpanel"на содержимом вкладок,aria-selectedна текущей вкладке,aria-controlsиaria-labelledbyсвязывают вкладку с панелью и наоборот,tabindex="-1"на неактивных вкладках, чтобы фокус шел только по активной вкладке, а переключение происходило стрелками.
Этот паттерн рекомендует сама спецификация WAI-ARIA Authoring Practices.
Роли и состояния: связка с aria-* атрибутами
Одна из сильных сторон ARIA — возможность описывать не только тип компонента (роль), но и его состояние. Многие состояния привязаны к конкретным ролям.
Пример: чекбокс на диве
Давайте разберемся на примере кастомного чекбокса.
<div
role="checkbox"
aria-checked="false"
tabindex="0"
id="subscribe"
>
Подписаться на новости
</div>
<script>
const checkbox = document.getElementById("subscribe")
function toggleCheckbox() {
// Здесь мы читаем текущее состояние
const isChecked = checkbox.getAttribute("aria-checked") === "true"
const newValue = !isChecked
// Обновляем состояние атрибута
checkbox.setAttribute("aria-checked", newValue ? "true" : "false")
console.log("Состояние чекбокса", newValue)
}
checkbox.addEventListener("click", toggleCheckbox)
checkbox.addEventListener("keydown", event => {
// Реагируем на пробел, как это делает нативный чекбокс
if (event.key === " ") {
event.preventDefault()
toggleCheckbox()
}
})
</script>За счет связки:
role="checkbox"— тип компонента,aria-checked— состояние (отмечен/нет),
скринридер может корректно озвучивать переключения.
Лучшие практики использования ARIA-ролей
Используйте HTML, а не ARIA, когда это возможно
ARIA — не замена семантическому HTML, а его дополнение. Если вы можете написать:
<button type="button">Отправить</button>то это всегда лучше, чем:
<div role="button" tabindex="0">Отправить</div>Принцип «Сначала HTML, потом ARIA» экономит вам время и уменьшает риск ошибок.
Не переопределяйте семантику без необходимости
Вот такой код выглядит «переусердствованием»:
<button role="button">Сохранить</button>
<nav role="navigation">...</nav>
<h2 role="heading">Раздел</h2>Все эти роли уже заданы самим HTML. Лишние атрибуты:
- загромождают разметку,
- могут вводить в заблуждение, если кто-то потом начнет их менять.
Никогда не ставьте несогласованные роли
Примеры, которых стоит избегать:
<a href="/home" role="button">На главную</a>
<button role="link">Открыть профиль</button>Почему это проблема:
- визуально элемент выглядит, как ссылка или кнопка,
- а скринридер объявляет его по-другому,
- пользователь с нарушением зрения будет ожидать одного поведения, а получит другое.
Если нужно, чтобы ссылка выглядела как кнопка — стилизуйте ее через CSS, но не меняйте роль.
Не используйте несуществующие роли
Иногда разработчики придумывают свои значения:
<div role="card">...</div>
<div role="tooltip-popup">...</div>Такие значения просто игнорируются. Скринридер ничего полезного не узнает. Используйте только роли, определенные в спецификации WAI-ARIA.
Типичные ошибки при работе с ARIA-ролями
Ошибка 1. Роль без поведения
Например, так:
<div role="button">Кликните сюда</div>А дальше нет ни tabindex, ни обработки клавиатуры. В результате:
- элемент не фокусируется с клавиатуры,
- активировать его можно только мышью,
- пользователь, который пользуется клавиатурой, «застревает».
Минимально правильная версия:
<div
role="button"
tabindex="0"
onclick="doSomething()"
onkeydown="if(event.key==='Enter' || event.key===' '){event.preventDefault();doSomething();}"
>
Кликните сюда
</div>Здесь я размещаю пример, чтобы вам было проще понять, что ARIA-роль должна идти в паре с реальным поведением, а не подменять его.
Ошибка 2. Дублирование ролей
<nav role="navigation" aria-label="Основная навигация">
...
</nav>Формально это не ошибка, но лишняя роль здесь ничего не дает. Правильно:
<nav aria-label="Основная навигация">
...
</nav>Ошибка 3. Роль без контекста
Некоторые роли должны использоваться только внутри определенного контейнера. Например:
role="tab"только внутриrole="tablist",role="row"/role="cell"— внутриrole="table"илиrole="grid",role="menuitem"— внутриrole="menu".
Если вы расставите эти роли по отдельности, без правильной структуры, скринридеры могут вести себя непредсказуемо.
Проверка корректности использования ARIA-ролей
Автоматические проверки
Вы можете использовать:
- встроенные инструменты доступности в браузере (Lighthouse в Chrome, Accessibility Insights),
- линтеры (например, ESLint с плагином
jsx-a11yдля React), - валидаторы W3C.
Такие инструменты находят:
- недопустимые комбинации ролей,
- неизвестные значения ролей,
- отсутствие связанных атрибутов (например,
aria-labelledbyбез соответствующего id).
Ручное тестирование
Полностью полагаться только на автоматические проверки нельзя. Рекомендую вам:
- Пройтись по интерфейсу с клавиатуры (Tab, Shift+Tab, Enter, Space, стрелки).
- Открыть страницу с включенным скринридером (NVDA, JAWS, VoiceOver) и посмотреть, что он объявляет.
- Обратить внимание, не путают ли роли и названия пользователя (например, «ссылка» там, где визуально кнопка, и наоборот).
Роли в компонентах и фреймворках
React и JSX
В JSX вы задаете роли так же, как в обычном HTML:
function IconButton({ label, onClick }) {
return (
<button type="button" aria-label={label} onClick={onClick}>
{/* Здесь иконка без текста */}
<svg aria-hidden="true" focusable="false">
{/* ... */}
</svg>
</button>
)
}Частая ошибка в React — использовать <div> и <span> везде, а role добавлять «по ощущениям». Лучший подход — сразу выбирать подходящий HTML-элемент, а к ARIA-ролям прибегать только при создании сложных виджетов.
Компонентные библиотеки
Многие готовые библиотеки (Material UI, Bootstrap, Headless UI и др.) уже реализуют ARIA-роллирование «под капотом». Но понимать, что происходит, все равно полезно:
- вы сможете оценить, насколько библиотека действительно доступна,
- вы сможете правильно доработать компонент, не ломая семантику.
Например, Headless UI для вкладок автоматически расставляет role="tab", role="tablist", role="tabpanel" и управляет состояниями.
Заключение
ARIA-роль — это способ дать вашему интерфейсу четкую семантику для вспомогательных технологий. Она не заменяет HTML и не делает компонент доступным «сама по себе». Роль должна:
- соответствовать реальному поведению элемента,
- сочетаться с корректным состоянием (
aria-*атрибутами), - быть частью продуманной структуры (особенно в составных компонентах — меню, вкладки, таблицы).
Хорошая отправная точка — использовать семантический HTML везде, где это возможно, а ARIA подключать там, где вы делаете кастомные виджеты или нестандартные структуры. Так вы избежите многих типичных ошибок и упростите поддержку кода.
Частозадаваемые технические вопросы по теме ARIA-роль
1. Как узнать, какая неявная роль у элемента и нужно ли задавать role вручную
Откройте DevTools в браузере, используйте вкладку Accessibility. Там вы увидите «Computed role». Если этот результат вас устраивает, явный role добавлять не нужно. Дополнительно можно посмотреть таблицы соответствий HTML → ARIA в документации W3C.
2. Как правильно скрыть элемент от скринридера, но оставить в DOM
Используйте aria-hidden="true" на контейнере, который нужно скрыть. Пример:
<div aria-hidden="true">
<!-- Скрыто от скринридера, но видно визуально -->
</div>Важно не ставить aria-hidden="true" на интерактивные элементы, которыми пользователь реально должен пользоваться.
3. Можно ли использовать несколько ролей на одном элементе
Нет, атрибут role принимает список, но фактически скринридер использует первую поддерживаемую роль. Обычно пишут одну четкую роль. Списки вида role="button checkbox" только запутают поведение и не нужны.
4. Что делать, если компонент должен вести себя и как кнопка, и как ссылка
Определите основную функцию: переход на другую страницу/адрес — ссылка; действие в текущем интерфейсе — кнопка. Используйте соответствующий HTML-элемент (<a> или <button>) и не пытайтесь смешивать роли.
5. Как тестировать ARIA-роллирование без установки скринридера
Используйте:
- вкладку Accessibility в DevTools,
- Lighthouse (раздел Accessibility),
- расширения вроде axe DevTools.
Они покажут вычисленные роли, иерархию доступности и подсветят явные ошибки (несовместимые роли, отсутствующие связи aria-labelledby и т. п.).
Постройте личный план изучения Html до уровня Middle — бесплатно!
Html — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Все гайды по Html
Лучшие курсы по теме

HTML и CSS
Антон Ларичев
TypeScript с нуля
Антон Ларичев