Tabbable — это утилита для JavaScript, позволяющая точно
определять, какие элементы на странице могут получать фокус при
навигации с помощью клавиши Tab. Она используется для управления
доступностью интерфейсов, особенно в сложных веб-приложениях, где
элементы динамически появляются, исчезают или находятся внутри модальных
окон и вложенных контейнеров.
Главная задача библиотеки — определить табулируемые элементы (tabbable elements) и элементы, которые фокусируемы, но не включаются в порядок табуляции (focusable but not tabbable).
Пример базового использования:
import { tabbable } from 'tabbable';
const container = document.querySelector('#form');
const focusableElements = tabbable(container);
console.log(focusableElements);
В результате возвращается массив элементов, которые будут фокусироваться при навигации с клавишей Tab в указанном контейнере.
Focusable элементы — это все элементы, на которых
можно установить фокус программно или с помощью мыши
(.focus()), включая скрытые элементы с
tabindex="-1".
Tabbable элементы — это subset focusable элементов,
которые участвуют в стандартной табуляции (tabindex="0" или
естественно фокусируемые элементы: <input>,
<button>, <a href>).
import { focusable } from 'tabbable';
const allFocusable = focusable(document.body);
console.log(allFocusable); // Включает элементы с tabindex=-1
Разделение важно для реализации циклической навигации по модальным окнам и управления порядком табуляции.
Tabbable позволяет учитывать пользовательские атрибуты и
кастомные правила:
tabindex="-1" — исключает элемент из табуляции, но
оставляет фокусируемым.tabindex="0" — включает элемент в стандартный
порядок.tabindex="1..n" — задаёт приоритетный порядок (не
рекомендуется использовать слишком часто, нарушает естественную
навигацию).const options = {
includeContainer: true, // включить сам контейнер в проверку
displayCheck: 'full' // учитывать только видимые элементы
};
const elements = tabbable(document.body, options);
displayCheck может принимать значения:
'full' — проверяет все CSS свойства
(display, visibility, opacity)
для определения видимости.'legacy-full' — старая версия проверки, учитывает также
элементы с clip-path и overflow.'none' — игнорирует визуальные свойства, возвращает все
потенциально фокусируемые элементы.В крупных интерфейсах часто возникает ситуация, когда DOM содержит
тысячи элементов, но только часть из них видна
одновременно. Прямой вызов tabbable для всего контейнера
создаёт нагрузку на браузер.
Решение — виртуализация и ленивый подсчёт:
Пример реализации ленивой табуляции:
function getVisibleTabbable(container) {
const visibleNodes = Array.from(container.children).filter(el => el.offsetParent !== null);
return visibleNodes.flatMap(el => tabbable(el));
}
const visibleTabbable = getVisibleTabbable(document.querySelector('#virtual-list'));
Преимущество: уменьшение вычислительной нагрузки и предотвращение “залипания” интерфейса при работе с длинными списками.
Если элементы добавляются или удаляются, массив tabbable элементов устаревает. Для корректной работы необходимо:
MutationObserver для отслеживания
изменений в контейнере.const container = document.querySelector('#dynamic-form');
const observer = new MutationObserver(() => {
const updatedTabbable = tabbable(container);
console.log('Обновлённый список:', updatedTabbable);
});
observer.observe(container, { childList: true, subtree: true });
Это особенно важно при работе с модальными окнами, аккордеонами и вкладками, где содержимое появляется после пользовательских действий.
Библиотека Tabbable часто используется вместе с утилитами для управления фокусом:
const modal = document.querySelector('#modal');
const elements = tabbable(modal);
elements[0].focus(); // первый tabbable элемент
elements[elements.length - 1].focus(); // последний
modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
const elements = tabbable(modal);
const first = elements[0];
const last = elements[elements.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
});
Такой подход гарантирует, что пользователь не выйдет за пределы модального окна при навигации клавишей Tab.
Пример с Shadow DOM:
const shadowHost = document.querySelector('#shadow-host');
const shadowRoot = shadowHost.shadowRoot;
const elements = tabbable(shadowRoot, { getShadowRoot: true });
subtree) для контейнеров с
большим количеством элементов.react-virtualized) для минимизации DOM
операций.tabindex и
видимости.Такой подход обеспечивает точное управление навигацией с клавиатуры и высокую производительность даже в сложных, динамически изменяемых интерфейсах.