Библиотека Tabbable предназначена для управления
фокусом элементов на веб-странице. Основная её задача — определять,
какие элементы могут быть фокусируемыми с помощью клавиши
Tab или Shift + Tab, а также
предоставлять информацию о порядке навигации. Tabbable работает с любыми
элементами DOM, включая стандартные интерактивные элементы
(input, button, select,
textarea, a[href]) и кастомные компоненты,
имеющие атрибут tabindex.
Метод tabbable(container, options) возвращает массив
элементов внутри контейнера, которые могут быть доступны для фокуса.
Пример базового использования:
import { tabbable } from 'tabbable';
const container = document.querySelector('#form');
const focusableElements = tabbable(container);
console.log(focusableElements);
В массиве будут только элементы, доступные для навигации через
Tab, исключая скрытые или отключенные элементы
(display: none, visibility: hidden,
disabled).
Опции функции tabbable позволяют
уточнять поведение:
includeContainer — если true, сам
контейнер проверяется на фокусируемость.getShadowRoot — функция для работы с Web Components и
теневыми DOM.tabindex — учитывает ли элементы с отрицательным
tabindex.Tabbable автоматически учитывает натуральный порядок
DOM и атрибут tabindex. Элементы с положительным
tabindex получают приоритет перед стандартным порядком, что
позволяет задавать кастомный путь навигации. Элементы с
tabindex="0" включаются в стандартный поток, а элементы с
отрицательным tabindex исключаются из навигации по Tab.
Пример сортировки:
const elements = tabbable(container);
// Элементы с tabindex > 0 идут первыми
elements.forEach(el => console.log(el, el.tabIndex));
Циклическая навигация необходима для модальных окон и интерфейсов, где фокус должен оставаться внутри определённого контейнера. Tabbable позволяет реализовать её, используя массив фокусируемых элементов.
Пример реализации цикла:
const modal = document.querySelector('#modal');
const focusable = tabbable(modal);
modal.addEventListener('keydown', (event) => {
if (event.key === 'Tab') {
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
}
});
В этом примере:
event.preventDefault() предотвращает уход фокуса за
пределы контейнера.Если внутри контейнера появляются новые интерактивные элементы,
массив focusable необходимо обновлять, чтобы цикл оставался
корректным:
function updateFocusable() {
focusable = tabbable(modal);
}
const observer = new MutationObserver(updateFocusable);
observer.observe(modal, { childList: true, subtree: true });
Таким образом, любая динамическая вставка элементов автоматически учитывается в цикле.
tabindex для цикловДля тонкой настройки можно изменять tabindex элементов
вручную, чтобы исключать часть из них из цикла или менять порядок:
firstElement.tabIndex = 0;
lastElement.tabIndex = 0;
hiddenButton.tabIndex = -1; // исключаем из цикла
Важно, что Tabbable учитывает эти значения при формировании массива элементов для навигации.
Tabbable хорошо работает с:
getShadowRoot
позволяет обрабатывать кастомные элементы внутри Web Components.Пример с Web Component:
const focusable = tabbable(customElement, {
getShadowRoot: (node) => node.shadowRoot
});
event.preventDefault() при реализации
циклической навигации.tabindex у всех интерактивных элементов.getShadowRoot.tabindex, чтобы
не нарушать естественный порядок Tab.Циклическая навигация с помощью Tabbable обеспечивает предсказуемое поведение интерфейса, повышает доступность и позволяет создавать безопасные, фокусируемые компоненты без необходимости вручную отслеживать каждый элемент DOM.