иконка discount

Скидка 15% по промокоду

кибер понедельник до 01.12иконка discount
CYBER2025
логотип PurpleSchool
логотип PurpleSchool

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).

Ручное тестирование

Полностью полагаться только на автоматические проверки нельзя. Рекомендую вам:

  1. Пройтись по интерфейсу с клавиатуры (Tab, Shift+Tab, Enter, Space, стрелки).
  2. Открыть страницу с включенным скринридером (NVDA, JAWS, VoiceOver) и посмотреть, что он объявляет.
  3. Обратить внимание, не путают ли роли и названия пользователя (например, «ссылка» там, где визуально кнопка, и наоборот).

Роли в компонентах и фреймворках

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 titleARIA label в HTML - подробное руководство по доступным интерфейсамСтрелочка вправо

Постройте личный план изучения Html до уровня Middle — бесплатно!

Html — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Все гайды по Html

Тег section в HTML - семантическая разметка структуры страницыТег nav в HTML - полное руководство по семантической навигацииТег main в HTML - подробное руководство по использованиюТег header в HTML - полное практическое руководствоТег footer в HTML - назначение семантика и практические примерыТег figure в HTML - как правильно оформлять иллюстрации и подписиТег aside в HTML - назначение правильная семантика и примерыТег figcaption в HTML - подробное руководство с примерами
Текстовая область HTML textarea - практическое руководствоВыпадающий список HTML select - полное руководство для разработчиковОпция списка HTML option - как работает и как правильно использоватьАтрибут method в HTML - как правильно отправлять данные формыЗаголовок группы HTML legend - как правильно использовать и оформлятьТег input в HTML - типы атрибуты валидация и примерыТег формы form в HTMLГруппа полей HTML fieldsetАтрибут action в HTML - как правильно задавать адрес отправки формы
Открыть базу знаний

Лучшие курсы по теме

изображение курса

HTML и CSS

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий