Компонент вкладок относится к группе композитных элементов управления. Визуально он выглядит как набор переключателей, позволяющих отображать разные панели содержимого. Для зрячего пользователя структура очевидна: активная вкладка подсвечена, а связанная с ней панель отображается на экране. Однако вспомогательные технологии — экранные дикторы, клавиатурная навигация и альтернативные устройства ввода — не способны интерпретировать такие интерфейсы без дополнительной семантики.
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.
Активная вкладка получает:
tabindex="0"
Неактивные:
tabindex="-1"
Пример:
<button role="tab" tabindex="0">Описание</button>
<button role="tab" tabindex="-1">Характеристики</button>
<button role="tab" tabindex="-1">Отзывы</button>
Фокус всегда находится только на одной вкладке.
Реализация 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);
}
});
Фокус перемещается без необходимости вручную управлять сложными особенностями браузеров.
При выборе вкладки необходимо изменить:
aria-selectedtabindexПример функции:
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 допускает два режима работы вкладок.
Панель меняется при перемещении стрелками.
Преимущества:
Недостатки:
Стрелки только перемещают фокус, а активация выполняется клавишей 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-disabledИспользуется для временно недоступных вкладок.
<button role="tab" aria-disabled="true">
Такие вкладки остаются в DOM, но не активируются.
aria-expandedИногда применяется в гибридных интерфейсах (например, вкладки + аккордеон). Однако для стандартных вкладок этот атрибут не требуется.
Корректная реализация вкладок должна обеспечивать:
Типичные проблемы:
aria-controlstabindexaria-selected="true"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 упрощает управление фокусом и взаимодействием элементов интерфейса.