Библиотека Tabbable предназначена для определения
элементов на странице, которые могут получать фокус с помощью клавиши
Tab. Она анализирует DOM-структуру и возвращает массив
элементов, доступных для табуляции, учитывая их видимость, атрибуты
tabindex и состояния, такие как disabled или
hidden. Основной метод библиотеки —
tabbable(container), где container — корневой
элемент, внутри которого производится поиск фокусируемых элементов.
Пример базового использования:
import { tabbable } from 'tabbable';
const container = document.querySelector('#menu');
const focusableElements = tabbable(container);
console.log(focusableElements);
В результате возвращается массив DOM-элементов в порядке, соответствующем естественной последовательности табуляции.
Вложенные подменю представляют собой отдельные блоки внутри основного меню. Важно контролировать, какие элементы активны для табуляции, чтобы пользователь мог перемещаться по уровню меню без “прыжков” к элементам вне подменю.
Принципы работы с вложенными подменю:
Изоляция табуляции При открытии подменю необходимо ограничивать табуляцию элементами только внутри него. Это позволяет создать эффект “фокусного контейнера”, предотвращая случайное переключение на элементы родительского меню или страницы.
Динамическое обновление Так как подменю часто генерируется динамически, массив фокусируемых элементов следует пересчитывать при каждом открытии подменю:
function updateSubmenuTabbable(submenu) {
return tabbable(submenu);
}
Это гарантирует, что новые элементы, добавленные через JS, будут корректно доступны для навигации.
Учёт видимости Элементы, скрытые с помощью
display: none, visibility: hidden или
aria-hidden="true", автоматически исключаются из списка
tabbable, что предотвращает попадание фокуса на невидимые
объекты.
Элементы меню могут иметь атрибут tabindex, который
управляет порядком табуляции:
tabindex="0" — элемент включён в обычный порядок
табуляции.tabindex="-1" — элемент можно программно фокусировать,
но он исключён из обычного цикла Tab.tabindex="n" (n > 0) — приоритетный порядок фокуса,
но использование положительных значений не рекомендуется из-за
потенциальной путаницы в больших меню.Вложенные подменю обычно используют tabindex="0" для
всех интерактивных элементов, оставляя управление порядком через
естественный поток DOM.
Открытие подменю: первый фокусируемый элемент подменю автоматически получает фокус, чтобы пользователь мог сразу перемещаться по нему с клавиатуры:
const submenuElements = tabbable(submenu);
if (submenuElements.length > 0) {
submenuElements[0].focus();
}
Закрытие подменю: при закрытии фокус возвращается на элемент, который вызвал открытие подменю. Это создаёт логичную навигационную цепочку:
triggerElement.focus();
function getAllTabbable(menu) {
let elements = tabbable(menu);
const submenus = menu.querySelectorAll('.submenu');
submenus.forEach(sub => {
elements = elements.concat(getAllTabbable(sub));
});
return elements;
}
Управление клавишами навигации Помимо табуляции,
рекомендуется поддерживать навигацию стрелками. Tabbable предоставляет
только определение фокусируемых элементов, поэтому обработка
ArrowUp, ArrowDown, ArrowLeft,
ArrowRight реализуется вручную, перемещая фокус между
элементами массива.
Динамическое обновление состояния При изменении состояния подменю (например, открытие/закрытие, изменение доступности элементов) необходимо повторно вычислять tabbable-массив. Это предотвращает ошибки, когда фокус остаётся на удалённом или скрытом элементе.
tabindex="0" вместо положительных значений
для предсказуемой навигации.Эти подходы обеспечивают удобную и предсказуемую клавиатурную навигацию в сложных вложенных меню, минимизируя потерю фокуса и повышая доступность интерфейса.