Библиотека Tabbable предназначена для определения
элементов веб-страницы, на которые можно перемещаться с помощью клавиши
Tab. Она помогает выявлять фокусируемые
элементы и строить логику управления фокусом, что особенно
важно для создания доступных интерфейсов.
На вход функция tabbable() принимает DOM-элемент или
документ, возвращая массив элементов, которые могут быть фокусированы.
По умолчанию она учитывает:
<a>,
<button>, <input>,
<textarea>, <select>)tabindex, где значение ≥ 0contenteditable="true"Пример использования:
import { tabbable } from 'tabbable';
const container = document.querySelector('#modal');
const focusableElements = tabbable(container);
console.log(focusableElements);
Этот код вернёт все элементы внутри контейнера #modal,
на которые пользователь может перейти с помощью клавиши
Tab.
Tabindex управляет порядком обхода элементов при навигации с клавиатуры:
tabindex="0" — элемент доступен для фокуса в
естественном порядке документа.tabindex="-1" — элемент исключён из обхода
Tab, но его можно сфокусировать программно.tabindex > 0 — элемент получает приоритет перед
элементами с tabindex="0"; порядок определяется
числом.Tabbable корректно учитывает эти значения, возвращая элементы в порядке обхода, который соответствует поведению браузера.
Tabbable игнорирует элементы, которые:
display: none или
visibility: hiddenaria-hidden="true"disabledЭто позволяет строить логику фокусировки, не включая элементы, которые пользователь не видит и не может использовать.
Одна из ключевых задач Tabbable — создание ловушек
фокуса. Ловушка фокуса необходима, чтобы пользователь, открыв
модальное окно, не мог покинуть его при помощи Tab до
закрытия модалки.
Пример реализации ловушки фокуса:
const modal = document.getElementById('modal');
const focusableElements = tabbable(modal);
let firstElement = focusableElements[0];
let lastElement = focusableElements[focusableElements.length - 1];
modal.addEventListener('keydown', (event) => {
if (event.key !== 'Tab') return;
if (event.shiftKey) {
if (document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
}
} else {
if (document.activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}
});
Ключевой момент: при обходе Tab и
Shift+Tab происходит циклический перенос фокуса внутри
модального окна.
Tabbable подходит для работы с динамическими интерфейсами. Если внутри контейнера появляются новые интерактивные элементы, необходимо пересчитывать список фокусируемых элементов:
const refreshFocusableElements = () => {
focusableElements = tabbable(modal);
firstElement = focusableElements[0];
lastElement = focusableElements[focusableElements.length - 1];
};
Вызывая refreshFocusableElements после изменения DOM,
ловушка фокуса остаётся корректной.
Библиотека поддерживает дополнительные настройки:
getShadowRoot: возвращает shadow root для проверки
элементов внутри теневых DOM.includeContainer: если true, контейнер сам
будет включён в список фокусируемых элементов, если он соответствует
критериям.tabindexIgnore: позволяет игнорировать элементы с
определённым значением tabindex.Пример с опцией:
const focusable = tabbable(modal, { includeContainer: true });
Tabbable учитывает ARIA-атрибуты для корректного обхода:
aria-hidden="true" исключаются из
списка.role="dialog" для управления
фокусом внутри модальных окон.Это обеспечивает соответствие стандартам WCAG и упрощает работу с вспомогательными технологиями.
tabindex="-1" для элементов, которые
должны быть доступны только программно.getShadowRoot.Эти практики обеспечивают корректное управление фокусом и улучшение взаимодействия для пользователей клавиатуры.