Библиотека Tabbable предназначена для работы с
элементами интерфейса, которые могут получать фокус при навигации с
помощью клавиши Tab. Она обеспечивает точное определение
фокусируемых элементов, учитывая видимость, атрибуты и
вложенность в DOM. Это особенно важно для сложных интерфейсов с
модальными окнами, вкладками и динамически создаваемыми элементами.
Tabbable не изменяет DOM и не добавляет обработчиков событий по умолчанию; её задача — предоставить разработчику массив элементов, доступных для табуляции в порядке их естественного следования в DOM.
Элементы делятся на два типа:
<a> с href,
<input>, <button> и элементы с
tabindex ≥ 0.Tab. Все tabbable элементы также являются
focusable, но не все focusable элементы являются tabbable (например,
скрытые элементы).Пример использования библиотеки:
import { tabbable } from 'tabbable';
const container = document.getElementById('modal');
const tabbableElements = tabbable(container);
console.log(tabbableElements);
// [button#submit, input#name, a#help]
Ключевые моменты:
tabindex="-1" делает элемент focusable, но он не
tabbable.display: none или
visibility: hidden игнорируются.disabled также исключаются из списка
tabbable.Циклические ссылки возникают, когда в DOM создаются
элементы, которые ссылаются друг на друга через вложенные контейнеры или
через динамическое перемещение. Например, модальные окна внутри вкладок,
вкладки внутри аккордеонов и так далее. Без правильной обработки это
может привести к неожиданным результатам при навигации с
Tab, включая зацикливание фокуса или пропуск элементов.
Подход Tabbable:
tabindex Элементы с положительным
tabindex получают приоритет при определении порядка. Это
важно, когда циклическая структура может содержать элементы с
нестандартной табуляцией.Пример обработки цикла:
const containerA = document.getElementById('tabA');
const containerB = document.getElementById('tabB');
// допустим, containerB вложен в containerA через условный рендеринг
const allTabbable = [
...tabbable(containerA),
...tabbable(containerB)
].filter((el, index, self) => self.indexOf(el) === index);
console.log(allTabbable);
Для интерфейсов с динамическим показом/скрытием
элементов важна проверка видимости перед вызовом
tabbable. Это позволяет избежать включения невидимых
элементов в порядок табуляции.
Пример:
function getVisibleTabbable(modal) {
const elements = tabbable(modal);
return elements.filter(el => el.offsetParent !== null);
}
В некоторых случаях требуется зацикливание фокуса
внутри модального окна или вкладки. Для этого комбинируют
tabbable с обработкой событий клавиш:
const modal = document.getElementById('modal');
const elements = tabbable(modal);
modal.addEventListener('keydown', (event) => {
if (event.key === 'Tab') {
const first = elements[0];
const last = elements[elements.length - 1];
if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
} else if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
}
}
});
Особенности:
shift + Tab позволяет корректно зациклить
фокус в обратном порядке.display: none или скрытый через aria-hidden
должен исключаться.aria и accessibilityTabbable корректно учитывает атрибуты ARIA:
aria-hidden="true" игнорируются.role="dialog" или
role="tabpanel" могут использоваться для ограничения
области фокусировки.Это позволяет интегрировать библиотеку в сложные интерфейсы, соблюдая стандарты доступности.
Эти подходы обеспечивают точную и безопасную работу с фокусом в сложных циклических и динамических DOM-структурах, предотвращая ошибки навигации и поддерживая стандарты доступности.