Библиотека Tabbable предназначена для определения элементов на странице, которые могут быть сфокусированы с помощью клавиши Tab. Она упрощает управление навигацией по интерфейсу и обеспечивает корректное поведение фокуса в сложных компонентах.
Основная функция библиотеки возвращает массив элементов, доступных
для табуляции. Эти элементы включают стандартные интерактивные теги
(<a>, <button>,
<input>, <textarea>,
<select>) и элементы с установленным атрибутом
tabindex, где tabindex >= 0.
import tabbable from 'tabbable';
const elements = tabbable(document.body);
console.log(elements); // массив элементов доступных для табуляции
Важно: tabindex="-1" делает элемент
фокусируемым программно, но исключает из последовательности Tab.
В интерфейсах, где элементы расположены в виде сетки или списка, часто требуется управление фокусом не только клавишей Tab, но и стрелками. Tabbable помогает определить точки фокусировки, а управление стрелками реализуется на основе полученного массива.
const container = document.querySelector('.menu');
const focusableItems = tabbable(container);
focusableItems будет упорядочен в порядке естественной
табуляции по DOM. Для навигации стрелками необходимо хранить индекс
текущего элемента:
let currentIndex = 0;
focusableItems[currentIndex].focus();
Навигация стрелками реализуется через обработчик события
keydown. Для примера списка вертикальной навигации:
container.addEventListener('keydown', (event) => {
const key = event.key;
if (key === 'ArrowDown') {
currentIndex = (currentIndex + 1) % focusableItems.length;
focusableItems[currentIndex].focus();
event.preventDefault();
}
if (key === 'ArrowUp') {
currentIndex = (currentIndex - 1 + focusableItems.length) % focusableItems.length;
focusableItems[currentIndex].focus();
event.preventDefault();
}
});
Особенности:
event.preventDefault() предотвращает стандартное
поведение браузера (например, прокрутку страницы).Для сеток или панелей с двухмерной структурой необходимо учитывать как вертикальные, так и горизонтальные стрелки:
if (key === 'ArrowRight') {
currentIndex = (currentIndex + 1) % focusableItems.length;
focusableItems[currentIndex].focus();
}
if (key === 'ArrowLeft') {
currentIndex = (currentIndex - 1 + focusableItems.length) % focusableItems.length;
focusableItems[currentIndex].focus();
}
Для более сложных сеток рекомендуется хранить элементы в двумерном массиве, соответствующем визуальной структуре, и изменять индекс по строкам и столбцам.
Если DOM изменяется (добавляются или удаляются элементы), массив элементов Tabbable становится устаревшим. Необходимо обновлять его при каждом изменении:
function updateFocusable() {
focusableItems = tabbable(container);
if (currentIndex >= focusableItems.length) {
currentIndex = focusableItems.length - 1;
}
}
Эту функцию вызывают после динамического добавления, удаления или скрытия элементов.
Tabbable позволяет игнорировать элементы по условию. Например, скрытые или отключённые элементы не включаются автоматически, но иногда требуется дополнительная фильтрация:
const visibleFocusable = tabbable(container, { includeOnlyTabbable: true });
Параметр includeOnlyTabbable гарантирует исключение
элементов с tabindex=-1 и скрытых элементов
(display: none или visibility: hidden).
Стандартная навигация клавишей Tab может сочетаться с управлением стрелками. Рекомендуется:
focusin
событие).Пример отслеживания текущего элемента при Tab:
container.addEventListener('focusin', (event) => {
currentIndex = focusableItems.indexOf(event.target);
});
Это позволяет стрелкам работать корректно независимо от того, был ли фокус установлен через Tab или программно.