ARIA-атрибуты для табов

Компонент вкладок относится к группе композитных элементов управления. Визуально он выглядит как набор переключателей, позволяющих отображать разные панели содержимого. Для зрячего пользователя структура очевидна: активная вкладка подсвечена, а связанная с ней панель отображается на экране. Однако вспомогательные технологии — экранные дикторы, клавиатурная навигация и альтернативные устройства ввода — не способны интерпретировать такие интерфейсы без дополнительной семантики.

ARIA-атрибуты (Accessible Rich Internet Applications) позволяют описать структуру и состояние вкладок таким образом, чтобы вспомогательные технологии могли корректно взаимодействовать с интерфейсом.

Библиотека ally.js используется для управления фокусом, навигацией и доступностью интерфейса. В сочетании с ARIA она обеспечивает корректное поведение вкладок при клавиатурной навигации и изменении состояния элементов.


Семантическая структура вкладок

ARIA определяет стандартную модель вкладок, состоящую из нескольких ключевых ролей.

Основные роли

Роль Назначение
tablist контейнер для вкладок
tab отдельная вкладка
tabpanel панель содержимого

Минимальная структура:

<div role="tablist">
  <button role="tab" aria-controls="panel-1" id="tab-1">Описание</button>
  <button role="tab" aria-controls="panel-2" id="tab-2">Характеристики</button>
  <button role="tab" aria-controls="panel-3" id="tab-3">Отзывы</button>
</div>

<div id="panel-1" role="tabpanel" aria-labelledby="tab-1">
  ...
</div>

<div id="panel-2" role="tabpanel" aria-labelledby="tab-2">
  ...
</div>

<div id="panel-3" role="tabpanel" aria-labelledby="tab-3">
  ...
</div>

Такое описание сообщает вспомогательным технологиям:

  • существует список вкладок;
  • каждая вкладка управляет конкретной панелью;
  • каждая панель связана со своей вкладкой.

Связь вкладки и панели

Связь устанавливается двумя атрибутами.

aria-controls

Указывает, какой элемент управляется вкладкой.

<button role="tab" aria-controls="panel-1">

aria-labelledby

Используется в панели для ссылки на вкладку.

<div role="tabpanel" aria-labelledby="tab-1">

Так формируется двусторонняя связь:

tab → aria-controls → panel
panel → aria-labelledby → tab

Экранные дикторы благодаря этому могут сообщать:

  • название вкладки
  • принадлежность панели
  • текущее состояние интерфейса

Состояние активной вкладки

ARIA определяет специальный атрибут состояния:

aria-selected

Он сообщает, какая вкладка является активной.

<button role="tab" aria-selected="true">

Для остальных вкладок:

aria-selected="false"

Пример:

<button role="tab" aria-selected="true">Описание</button>
<button role="tab" aria-selected="false">Характеристики</button>
<button role="tab" aria-selected="false">Отзывы</button>

Экранный диктор сообщает:

Описание, вкладка, выбрана

Управление доступностью панелей

Активная панель должна быть доступна, остальные — скрыты.

Используются следующие механизмы.

hidden

Простой и семантически корректный способ скрытия.

<div role="tabpanel" hidden>

aria-hidden

Иногда используется в сложных интерфейсах.

<div role="tabpanel" aria-hidden="true">

Однако предпочтительным считается использование hidden, поскольку браузеры автоматически исключают такие элементы из доступности.


Управление фокусом

Вкладки являются одним из типичных элементов, управляемых стрелками клавиатуры.

ARIA предполагает специальную модель:

  • Tab перемещает фокус в список вкладок
  • стрелки переключают вкладки

Чтобы реализовать эту модель, используется roving tabindex.

roving tabindex

Активная вкладка получает:

tabindex="0"

Неактивные:

tabindex="-1"

Пример:

<button role="tab" tabindex="0">Описание</button>
<button role="tab" tabindex="-1">Характеристики</button>
<button role="tab" tabindex="-1">Отзывы</button>

Фокус всегда находится только на одной вкладке.


Использование ally.js для управления фокусом

Реализация roving tabindex и перемещения фокуса значительно упрощается при использовании ally.js.

Библиотека предоставляет инструменты для:

  • поиска фокусируемых элементов
  • программного перемещения фокуса
  • управления доступностью

Установка фокуса

import ally from 'ally.js';

const tab = document.querySelector('#tab-2');
ally.element.focus(tab);

Это гарантирует корректную установку фокуса во всех браузерах.


Навигация стрелками

ARIA определяет стандартные клавиши для вкладок:

Клавиша Действие
ArrowRight следующая вкладка
ArrowLeft предыдущая
Home первая вкладка
End последняя

Пример обработки:

tablist.addEventListener('keydown', event => {
  const tabs = Array.from(tablist.querySelectorAll('[role="tab"]'));
  const currentIndex = tabs.indexOf(document.activeElement);

  if (event.key === 'ArrowRight') {
    const next = tabs[(currentIndex + 1) % tabs.length];
    ally.element.focus(next);
  }

  if (event.key === 'ArrowLeft') {
    const prev = tabs[(currentIndex - 1 + tabs.length) % tabs.length];
    ally.element.focus(prev);
  }
});

Фокус перемещается без необходимости вручную управлять сложными особенностями браузеров.


Активация вкладки

При выборе вкладки необходимо изменить:

  1. aria-selected
  2. tabindex
  3. видимость панели
  4. фокус

Пример функции:

function activateTab(tab) {
  const tabs = document.querySelectorAll('[role="tab"]');
  const panels = document.querySelectorAll('[role="tabpanel"]');

  tabs.forEach(t => {
    t.setAttribute('aria-selected', 'false');
    t.setAttribute('tabindex', '-1');
  });

  panels.forEach(panel => {
    panel.hidden = true;
  });

  tab.setAttribute('aria-selected', 'true');
  tab.setAttribute('tabindex', '0');

  const panel = document.getElementById(tab.getAttribute('aria-controls'));
  panel.hidden = false;

  ally.element.focus(tab);
}

Автоматическая и ручная активация

ARIA допускает два режима работы вкладок.

Automatic activation

Панель меняется при перемещении стрелками.

Преимущества:

  • быстрая навигация
  • меньше действий

Недостатки:

  • возможна задержка загрузки контента

Manual activation

Стрелки только перемещают фокус, а активация выполняется клавишей Enter или Space.

Пример:

tab.addEventListener('keydown', event => {
  if (event.key === 'Enter' || event.key === ' ') {
    activateTab(event.currentTarget);
  }
});

Этот режим предпочтителен, если панели содержат сложный или тяжёлый контент.


Вертикальные вкладки

ARIA поддерживает вертикальную ориентацию.

Для этого используется:

<div role="tablist" aria-orientation="vertical">

Клавиатура изменяется:

Клавиша Действие
ArrowDown следующая вкладка
ArrowUp предыдущая

Дополнительные ARIA-атрибуты

aria-disabled

Используется для временно недоступных вкладок.

<button role="tab" aria-disabled="true">

Такие вкладки остаются в DOM, но не активируются.


aria-expanded

Иногда применяется в гибридных интерфейсах (например, вкладки + аккордеон). Однако для стандартных вкладок этот атрибут не требуется.


Проверка доступности

Корректная реализация вкладок должна обеспечивать:

  1. доступность всех вкладок с клавиатуры
  2. корректное объявление состояния экранными дикторами
  3. наличие связи вкладка ↔︎ панель
  4. управление фокусом

Типичные проблемы:

  • отсутствие aria-controls
  • неправильный tabindex
  • несколько вкладок с aria-selected="true"
  • панели, скрытые только через CSS

ally.js помогает избежать части этих ошибок благодаря стабильному управлению фокусом и проверке фокусируемости элементов.


Типичная итоговая структура

<div role="tablist">
  <button id="tab-1"
          role="tab"
          aria-controls="panel-1"
          aria-selected="true"
          tabindex="0">
    Описание
  </button>

  <button id="tab-2"
          role="tab"
          aria-controls="panel-2"
          aria-selected="false"
          tabindex="-1">
    Характеристики
  </button>
</div>

<div id="panel-1"
     role="tabpanel"
     aria-labelledby="tab-1">
  Контент
</div>

<div id="panel-2"
     role="tabpanel"
     aria-labelledby="tab-2"
     hidden>
  Контент
</div>

Такой подход соответствует спецификации WAI-ARIA Authoring Practices, обеспечивает правильную навигацию с клавиатуры и корректную работу экранных дикторов, а использование ally.js упрощает управление фокусом и взаимодействием элементов интерфейса.