Компонент вкладок (tabs) используется для отображения нескольких логически связанных разделов интерфейса в одном и том же пространстве страницы. Каждая вкладка представляет отдельную панель содержимого, а переключение между ними выполняется без перезагрузки документа.
Такая структура широко применяется в веб-приложениях, административных панелях, настройках программ и сложных интерфейсах. Однако реализация вкладок должна учитывать не только визуальное взаимодействие, но и корректную работу клавиатурной навигации, фокуса и вспомогательных технологий.
В среде доступности вкладки рассматриваются как составной интерактивный виджет, состоящий из:
В спецификации WAI-ARIA эти элементы обычно обозначаются ролями:
tablisttabtabpanelПри разработке доступных вкладок необходимо обеспечить:
Библиотека ally.js предоставляет набор инструментов, облегчающих реализацию подобных механизмов.
При создании вкладочного интерфейса часто возникают следующие сложности:
1. Управление фокусом
При переключении вкладки необходимо:
2. Навигация стрелками
Согласно рекомендациям ARIA:
← и → переключают вкладкиHome — переход к первой вкладкеEnd — переход к последней вкладке3. Изоляция неактивных панелей
Неактивное содержимое должно:
4. Поддержка различных источников фокуса
Переходы могут происходить через:
Модульная архитектура ally.js помогает контролировать эти аспекты.
Минимальная доступная структура вкладочного интерфейса:
<div class="tabs" role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
Общие
</button>
<button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
Настройки
</button>
<button role="tab" aria-selected="false" 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" hidden>
Содержимое второй вкладки
</div>
<div id="panel-3" role="tabpanel" aria-labelledby="tab-3" hidden>
Содержимое третьей вкладки
</div>
Ключевые атрибуты:
| Атрибут | Назначение |
|---|---|
role="tablist" |
контейнер вкладок |
role="tab" |
элемент вкладки |
role="tabpanel" |
панель содержимого |
aria-controls |
связь вкладки и панели |
aria-selected |
активная вкладка |
Вкладки требуют точного контроля над тем, какой элемент
получает фокус. В этом помогает модуль
ally.element.focus.
Подключение:
import ally from 'ally.js';
Перемещение фокуса:
ally.element.focus(tabElement);
Такой подход гарантирует корректное поведение во всех браузерах, включая ситуации с:
Для управления переключением необходимо получить список вкладок.
const tabs = document.querySelectorAll('[role="tab"]');
Однако важно учитывать, какие элементы могут получать фокус. В этом помогает модуль:
ally.query.tabsequence
Он возвращает элементы в реальном порядке табуляции.
Пример:
const focusable = ally.query.tabsequence({
context: document.querySelector('.tabs')
});
Это позволяет работать только с элементами, которые реально участвуют в навигации.
Основная логика переключения включает:
Пример функции:
function activateTab(tab) {
const current = document.querySelector('[role="tab"][aria-selected="true"]');
if (current) {
current.setAttribute('aria-selected', 'false');
const panel = document.getElementById(
current.getAttribute('aria-controls')
);
panel.hidden = true;
}
tab.setAttribute('aria-selected', 'true');
const panel = document.getElementById(
tab.getAttribute('aria-controls')
);
panel.hidden = false;
ally.element.focus(tab);
}
ARIA-рекомендации определяют клавиатурное управление вкладками.
Основные клавиши:
| Клавиша | Действие |
|---|---|
| ArrowRight | следующая вкладка |
| ArrowLeft | предыдущая вкладка |
| Home | первая вкладка |
| End | последняя вкладка |
Обработчик:
document.querySelector('.tabs').addEventListener('keydown', event => {
const tabs = Array.from(document.querySelectorAll('[role="tab"]'));
const index = tabs.indexOf(document.activeElement);
if (event.key === 'ArrowRight') {
const next = tabs[(index + 1) % tabs.length];
activateTab(next);
}
if (event.key === 'ArrowLeft') {
const prev = tabs[(index - 1 + tabs.length) % tabs.length];
activateTab(prev);
}
if (event.key === 'Home') {
activateTab(tabs[0]);
}
if (event.key === 'End') {
activateTab(tabs[tabs.length - 1]);
}
});
Использование ally.element.focus() внутри
activateTab() гарантирует корректное поведение фокуса.
Когда панель скрыта, её содержимое не должно попадать в навигацию
клавишей Tab.
Для решения используется модуль:
ally.maintain.disabled
Он временно деактивирует все интерактивные элементы внутри контейнера.
Пример:
const disabled = ally.maintain.disabled({
context: document.getElementById('panel-2')
});
Позже ограничения можно снять:
disabled.disengage();
Этот механизм удобен при переключении вкладок.
Иногда требуется автоматически переместить фокус внутрь панели, а не оставлять его на вкладке.
Для этого можно найти первый фокусируемый элемент.
const first = ally.query.firstTabbable({
context: panel
});
Перемещение фокуса:
if (first) {
ally.element.focus(first);
}
Это особенно важно для:
При сложных интерфейсах полезно отслеживать изменения фокуса. В ally.js для этого используется:
ally.observe.shadowMutations
или стандартные события focusin.
Пример:
document.addEventListener('focusin', event => {
console.log('Фокус:', event.target);
});
Это помогает выявлять ошибки навигации.
Вкладки могут создаваться динамически — например, при открытии новых документов.
После добавления элемента необходимо:
Пример:
function addTab(title, content) {
const id = Date.now();
const tab = document.createElement('button');
tab.setAttribute('role', 'tab');
tab.id = `tab-${id}`;
tab.setAttribute('aria-controls', `panel-${id}`);
tab.textContent = title;
const panel = document.createElement('div');
panel.id = `panel-${id}`;
panel.setAttribute('role', 'tabpanel');
panel.setAttribute('aria-labelledby', tab.id);
panel.hidden = true;
panel.textContent = content;
document.querySelector('.tabs').appendChild(tab);
document.body.appendChild(panel);
}
После добавления вкладка становится частью общей системы навигации.
Иногда вкладки располагаются вертикально. В этом случае меняется управление клавиатурой:
| Клавиша | Действие |
|---|---|
| ArrowDown | следующая вкладка |
| ArrowUp | предыдущая вкладка |
ARIA указывает использовать атрибут:
aria-orientation="vertical"
Контейнер вкладок:
<div role="tablist" aria-orientation="vertical">
JavaScript-логика при этом остаётся почти идентичной.
1. Использование <div> без
ролей
Без ролей интерфейс становится недоступным для скринридеров.
2. Отсутствие управления фокусом
Фокус может оставаться на скрытом элементе.
3. Неправильная работа клавиатуры
Некоторые реализации поддерживают только мышь.
4. Отсутствие синхронизации ARIA-атрибутов
aria-selected должен соответствовать фактическому
состоянию вкладки.
Хорошая реализация вкладок включает несколько уровней:
Структура
Навигация
Фокус
Доступность
Библиотека ally.js предоставляет инструменты, позволяющие реализовать все эти механизмы в рамках одной архитектуры доступного пользовательского интерфейса.