Таб-интерфейс (tab interface) представляет собой компонент пользовательского интерфейса, позволяющий переключаться между несколькими логически связанными областями контента. В веб-разработке табы реализуются с использованием HTML, CSS и JavaScript, однако корректная доступность такого интерфейса требует дополнительной логики управления фокусом, клавиатурной навигацией и ARIA-атрибутами.
Библиотека ally.js предоставляет набор утилит, предназначенных для работы с доступностью (accessibility) и управлением фокусом. При построении табов она применяется для:
В рамках таб-интерфейсов особенно важна корректная поддержка вертикальной и горизонтальной ориентации вкладок, поскольку навигация с клавиатуры отличается.
При создании табов необходимо соблюдать спецификацию ARIA Authoring Practices, которая определяет набор ролей и атрибутов.
Контейнер, содержащий список вкладок, получает роль:
role="tablist"
Также указывается ориентация:
aria-orientation="horizontal"
aria-orientation="vertical"
Если атрибут не указан, по умолчанию предполагается горизонтальная ориентация.
Каждая вкладка получает:
role="tab"
Дополнительные атрибуты:
aria-selected="true" — активная вкладкаaria-controls="panel-id" — связь с панелью
контентаtabindex="0" — доступность для фокусаНеактивные вкладки обычно получают tabindex="-1".
Контент вкладки определяется ролью:
role="tabpanel"
Атрибуты:
aria-labelledby="tab-id"hidden или aria-hidden="true" для
неактивных панелейПример минимальной структуры:
<div class="tabs">
<div role="tablist" aria-orientation="horizontal">
<button role="tab" id="tab-1" aria-controls="panel-1" aria-selected="true">
Профиль
</button>
<button role="tab" id="tab-2" aria-controls="panel-2" aria-selected="false">
Настройки
</button>
<button role="tab" id="tab-3" aria-controls="panel-3" aria-selected="false">
Безопасность
</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>
<div id="panel-3" role="tabpanel" aria-labelledby="tab-3" hidden>
Контент безопасности
</div>
</div>
Одной из ключевых задач таб-интерфейса является правильное управление фокусом клавиатуры. Библиотека ally.js предоставляет модуль:
ally.query.focusable
Он позволяет определить все элементы, способные получать фокус.
Пример:
const tabs = ally.query.focusable({
context: document.querySelector('[role="tablist"]')
});
Полученный список содержит все вкладки, участвующие в навигации.
Для переключения активной вкладки необходимо:
aria-selected;tabindex;Пример функции переключения:
function activateTab(tab) {
const tablist = tab.closest('[role="tablist"]');
const tabs = tablist.querySelectorAll('[role="tab"]');
tabs.forEach(t => {
t.setAttribute('aria-selected', 'false');
t.setAttribute('tabindex', '-1');
});
tab.setAttribute('aria-selected', 'true');
tab.setAttribute('tabindex', '0');
const panels = document.querySelectorAll('[role="tabpanel"]');
panels.forEach(panel => panel.hidden = true);
const panel = document.getElementById(tab.getAttribute('aria-controls'));
panel.hidden = false;
tab.focus();
}
Горизонтальные вкладки располагаются в строку. Навигация осуществляется клавишами:
Обработка клавиатуры:
function handleHorizontalNavigation(event, tabs, index) {
if (event.key === 'ArrowRight') {
event.preventDefault();
const next = (index + 1) % tabs.length;
tabs[next].focus();
}
if (event.key === 'ArrowLeft') {
event.preventDefault();
const prev = (index - 1 + tabs.length) % tabs.length;
tabs[prev].focus();
}
}
Для определения текущего фокуса может использоваться:
ally.get.activeElement();
Этот метод корректно работает даже в сложных случаях, включая Shadow DOM.
Вертикальные вкладки располагаются колонкой. Основное отличие — использование других клавиш:
Контейнер табов должен иметь атрибут:
aria-orientation="vertical"
Пример разметки:
<div role="tablist" aria-orientation="vertical">
<button role="tab">Общие</button>
<button role="tab">Аккаунт</button>
<button role="tab">Уведомления</button>
</div>
Для поддержки обеих ориентаций необходимо определить тип навигации.
function getOrientation(tablist) {
return tablist.getAttribute('aria-orientation') || 'horizontal';
}
Обработчик клавиатуры:
function handleKeydown(event) {
const tab = event.target;
const tablist = tab.closest('[role="tablist"]');
const tabs = Array.from(tablist.querySelectorAll('[role="tab"]'));
const index = tabs.indexOf(tab);
const orientation = getOrientation(tablist);
if (orientation === 'horizontal') {
handleHorizontalNavigation(event, tabs, index);
}
if (orientation === 'vertical') {
handleVerticalNavigation(event, tabs, index);
}
}
Вертикальная навигация:
function handleVerticalNavigation(event, tabs, index) {
if (event.key === 'ArrowDown') {
event.preventDefault();
const next = (index + 1) % tabs.length;
tabs[next].focus();
}
if (event.key === 'ArrowUp') {
event.preventDefault();
const prev = (index - 1 + tabs.length) % tabs.length;
tabs[prev].focus();
}
}
При сложных интерфейсах вкладки могут находиться внутри модальных окон или других компонентов. В таких случаях применяется механизм focus trapping.
ally.js предоставляет инструмент:
ally.maintain.tabFocus
Он позволяет ограничить перемещение фокуса внутри заданного контейнера.
Пример:
const handle = ally.maintain.tabFocus({
context: document.querySelector('.tabs')
});
После завершения работы:
handle.disengage();
В некоторых ситуациях вкладка может быть визуально скрыта, но оставаться в DOM. Необходимо исключить её из навигации.
Для этого применяется:
ally.is.focusable(element)
Пример:
const availableTabs = tabs.filter(tab => ally.is.focusable(tab));
При переключении вкладок важно синхронизировать три состояния:
Нарушение этой синхронизации приводит к проблемам с экранными дикторами.
Правильная последовательность действий:
aria-selected="true" у новой;Иногда панели загружаются только при активации вкладки. В этом случае важно сохранить корректные ARIA-связи.
Пример:
if (!panel.dataset.loaded) {
loadContent(panel);
panel.dataset.loaded = true;
}
При этом aria-controls должен существовать заранее.
Если вкладка удаляется динамически, необходимо определить новый элемент фокуса. ally.js предоставляет функцию:
ally.get.activeElement()
Пример обработки:
const active = ally.get.activeElement();
if (!document.contains(active)) {
tabs[0].focus();
}
В адаптивных интерфейсах горизонтальные вкладки могут превращаться в вертикальные при уменьшении ширины экрана.
Пример:
if (window.innerWidth < 600) {
tablist.setAttribute('aria-orientation', 'vertical');
} else {
tablist.setAttribute('aria-orientation', 'horizontal');
}
Обработчики клавиатуры автоматически используют правильную навигацию.
Вертикальная ориентация обычно реализуется через flex-layout.
[role="tablist"][aria-orientation="vertical"] {
display: flex;
flex-direction: column;
}
Горизонтальный вариант:
[role="tablist"][aria-orientation="horizontal"] {
display: flex;
}
Активная вкладка:
[role="tab"][aria-selected="true"] {
font-weight: bold;
border-bottom: 2px solid currentColor;
}
Без tabindex вкладки могут быть недоступны для
клавиатуры.
Использование стрелок без учета ориентации нарушает рекомендации ARIA.
Если панель отображается, но вкладка не имеет
aria-selected="true", экранные дикторы сообщают
некорректную информацию.
При переключении вкладки фокус должен оставаться на вкладке, а не переходить в панель.
При работе с табами могут использоваться дополнительные возможности библиотеки:
Определение текущего фокуса
ally.get.activeElement()
Поиск фокусируемых элементов
ally.query.focusable()
Проверка фокусируемости
ally.is.focusable()
Ограничение области фокуса
ally.maintain.tabFocus()
Комбинация этих инструментов позволяет создавать полностью доступные интерфейсы вкладок как для горизонтального, так и для вертикального расположения.