Библиотека Tabbable предназначена для определения
элементов на странице, которые могут быть фокусируемыми с помощью
клавиши Tab. Граничные случаи при работе с
tabbable-элементами часто приводят к непредсказуемому поведению
интерфейса. Рассмотрим ключевые моменты и тонкости, которые следует
учитывать при работе с этой библиотекой.
Tabbable использует комбинацию стандартных HTML-атрибутов и свойств CSS для выявления элементов, доступных для фокусировки. Ключевые критерии включают:
tabindex с положительным или нулевым
значением.<input>,
<button>, <a href>,
<textarea> и т.п.).disabled и видимости
(display: none, visibility: hidden,
opacity: 0 в некоторых случаях).Важно: библиотека учитывает только видимые элементы.
Элементы с display: none или полностью скрытые через
visibility исключаются, даже если они имеют
tabindex.
Элементы с tabindex="-1" не входят в
последовательность табуляции, но остаются программно
фокусируемыми. Это часто используется для создания модальных окон или
кастомных интерактивных компонентов.
Пример использования:
import { tabbable } from 'tabbable';
const container = document.querySelector('#modal');
const focusableElements = tabbable(container); // Игнорирует элементы с tabindex="-1"
Граничный случай: элемент с
tabindex="-1" внутри контейнера не попадет в массив
tabbable, но может быть сфокусирован через
element.focus().
Tabbable учитывает несколько CSS-свойств:
display – none делает элемент
недоступным.visibility – hidden исключает элемент из
tabbable.opacity – полностью прозрачный элемент
(opacity: 0) по умолчанию остается
tabbable, если он визуально присутствует в DOM.pointer-events: none – не влияет на tabbable, элемент
можно фокусировать через клавиатуру.Граничный случай: элемент с
position: fixed или absolute и нулевым
размером (width: 0; height: 0) все еще может быть возвращен
библиотекой как tabbable, хотя пользователь физически его не увидит.
Tabbable поддерживает стандартный DOM, но shadow DOM и iframe требуют дополнительной обработки.
shadowRoot.contentDocument и вызвать
tabbable отдельно, так как контекст фрейма независим.const iframeDoc = document.querySelector('iframe').contentDocument;
const tabbableInsideIframe = tabbable(iframeDoc.body);
Граничный случай: элементы из разных документов нельзя комбинировать напрямую, требуется отдельный вызов для каждого контекста.
Tabbable вычисляет список фокусируемых элементов на момент
вызова. Если DOM изменяется динамически (например, через
innerHTML или appendChild), массив
устаревает.
Решение — повторный вызов функции tabbable после
добавления новых элементов:
container.appendChild(newButton);
const updatedFocusable = tabbable(container);
Граничный случай: скрытые элементы, ставшие видимыми после добавления, не попадают в результат, пока не обновить вызов.
Tabbable позволяет использовать кастомный фильтр для исключения элементов:
const focusable = tabbable(container, {
getShadowRoot: true,
includeContainer: true,
filter: (element) => !element.classList.contains('no-tab')
});
filter возвращает true, чтобы включить
элемент, и false — чтобы исключить.data-* и
стилями.Граничный случай: фильтр должен быть согласован с видимостью элементов, иначе элемент может быть физически невидимым, но считаться tabbable.
<svg> и дочерние
<text> или <g> не
фокусируются по умолчанию, даже с tabindex="0",
если браузер не поддерживает их фокусировку.<foreignObject> с HTML-контентом.Граничный случай: использование
tabindex на нестандартных элементах может работать
непоследовательно между браузерами.
Для модальных интерфейсов часто применяется focus trap, где tabbable-элементы ограничиваются внутри контейнера. Пример:
const modal = document.querySelector('#modal');
const focusables = tabbable(modal);
focusables[0].focus();
Tab последовательно переключается между элементами
внутри модального окна.Граничный случай: если в модальном окне нет tabbable-элементов, библиотека возвращает пустой массив, что может привести к невозможности закрыть окно клавиатурой без дополнительной логики.
hiddenaria-hidden="true" не исключает
элемент из tabbable, но делает его недоступным для
assistive-технологий.hidden полностью блокирует элемент, аналогично
display: none.Граничный случай: комбинация
aria-hidden и tabindex="0" может привести к
конфликту доступности — элемент фокусируем, но недоступен экранным
читателям.
tabindex >= 0.display: none,
hidden), disabled, элементы вне DOM-контейнера
(если includeContainer не активен).tabindex="-1", динамически добавленные элементы,
конфликт с ARIA-атрибутами.Правильное использование Tabbable позволяет создавать надежные, доступные интерфейсы с контролируемой навигацией клавиатурой, минимизируя неожиданные проблемы при фокусировке.