Библиотека Tabbable предназначена для определения элементов в DOM, которые могут получать фокус при навигации с помощью клавиши Tab. Основная задача — упрощение управления доступностью интерфейсов, особенно при создании модальных окон, всплывающих панелей и кастомных виджетов.
Tabbable анализирует элементы страницы и возвращает
список всех фокусируемых объектов, учитывая их видимость, атрибуты
tabindex и типы элементов, поддерживающих фокус.
Пример базового использования:
import { tabbable } from 'tabbable';
const container = document.querySelector('#modal');
const focusableElements = tabbable(container);
console.log(focusableElements);
В результате focusableElements будет массив элементов,
которые могут быть достигнуты клавишей Tab внутри
указанного контейнера.
Когда открывается модальное окно, фокус обычно перемещается на первый интерактивный элемент внутри него. При закрытии окна важно вернуть фокус к элементу, с которого пользователь начал взаимодействие. Это повышает доступность и предотвращает потерю контекста.
Перед открытием модального окна необходимо сохранить текущий активный элемент:
const previouslyFocusedElement = document.activeElement;
Это может быть кнопка открытия модального окна или любой интерактивный элемент страницы. Сохранённый элемент используется для восстановления фокуса после закрытия.
Используя Tabbable, можно динамически определить все фокусируемые элементы модального окна и организовать цикл навигации:
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') {
if (event.shiftKey) { // Shift + Tab
if (document.activeElement === firstElement) {
event.preventDefault();
lastElement.focus();
}
} else { // Tab
if (document.activeElement === lastElement) {
event.preventDefault();
firstElement.focus();
}
}
}
});
Такой подход предотвращает уход фокуса за пределы модального окна, создавая замкнутый фокусный контур.
После закрытия модального окна фокус возвращается на сохранённый элемент:
function closeModal() {
modal.style.display = 'none';
if (previouslyFocusedElement) {
previouslyFocusedElement.focus();
}
}
Этот механизм гарантирует, что пользователь возвращается к исходной точке интерфейса, сохраняя последовательность навигации клавиатурой.
В интерфейсах, где элементы модального окна создаются динамически, важно повторно вычислять список фокусируемых элементов после каждой генерации:
function updateFocusableElements() {
focusableElements = tabbable(modal);
firstElement = focusableElements[0];
lastElement = focusableElements[focusableElements.length - 1];
}
Этот метод обеспечивает корректное поведение Tab даже при изменении структуры DOM внутри модального окна.
display: none или
visibility: hidden) автоматически исключаются из списка
Tabbable.tabindex="-1" также
игнорируются библиотекой.tabindex, чтобы они корректно попадали в список
фокусируемых элементов.При использовании Tabbable в React, Vue или Svelte рекомендуется:
useEffect (React) или onMount
(Svelte) для добавления обработчиков клавиатуры и восстановления
фокуса.Пример для React:
import { useEffect, useRef } from 'react';
import { tabbable } from 'tabbable';
export function Modal({ isOpen, onClose }) {
const modalRef = useRef(null);
const previouslyFocused = useRef(null);
useEffect(() => {
if (isOpen) {
previouslyFocused.current = document.activeElement;
const elements = tabbable(modalRef.current);
elements[0]?.focus();
} else {
previouslyFocused.current?.focus();
}
}, [isOpen]);
return (
<div ref={modalRef} style={{ display: isOpen ? 'block' : 'none' }}>
{/* контент модального окна */}
</div>
);
}
Этот подход гарантирует плавное взаимодействие с клавиатурой и возвращение фокуса на исходный элемент после закрытия модального окна.
Такой подход позволяет создавать интерфейсы, полностью совместимые с принципами WCAG и удобные для пользователей, работающих с клавиатурой.