Фокус не попадает в ловушку

Библиотека Focus-trap предназначена для управления фокусом в веб-приложениях, обеспечивая его ограничение внутри определённого контейнера. Это особенно важно для модальных окон, выпадающих меню, диалогов и других компонентов, где пользовательский фокус не должен покидать активный элемент интерфейса до завершения взаимодействия. Фокусировка гарантирует корректную навигацию с клавиатуры, соответствие стандартам доступности (a11y) и предотвращает случайное взаимодействие с фоновым контентом.

Основная идея заключается в том, чтобы «замкнуть» фокус внутри контейнера, позволяя циклическое перемещение между элементами с помощью клавиши Tab. Любая попытка перемещения за пределы контейнера автоматически возвращается на первый или последний доступный элемент.


Установка и базовая инициализация

Для использования библиотеки необходимо установить пакет через npm или yarn:

npm install focus-trap

Или:

yarn add focus-trap

Создание фокустрапа выполняется следующим образом:

import { createFocusTrap } from 'focus-trap';

const modal = document.getElementById('modal');
const focusTrap = createFocusTrap(modal, {
  onActivate: () => modal.classList.add('is-active'),
  onDeactivate: () => modal.classList.remove('is-active'),
  initialFocus: '#modal input', // элемент, который получит фокус при активации
  escapeDeactivates: true,      // отключение ловушки по Esc
  clickOutsideDeactivates: true // отключение при клике вне контейнера
});

Активация и деактивация фокустрапа:

focusTrap.activate();
focusTrap.deactivate();

Ключевые опции

1. initialFocus Позволяет указать элемент, который получит фокус при активации ловушки. Значение может быть селектором CSS, DOM-элементом или функцией, возвращающей элемент.

2. fallbackFocus Элемент, на который фокус будет установлен, если initialFocus недоступен. Обычно указывается сам контейнер.

3. escapeDeactivates Булево значение, определяющее, будет ли ловушка деактивирована при нажатии клавиши Escape.

4. clickOutsideDeactivates Позволяет закрывать ловушку при клике за пределами контейнера. Принимает булево значение или функцию, возвращающую булево.

5. allowOutsideClick Разрешает взаимодействие с элементами вне ловушки, не деактивируя её.

6. returnFocusOnDeactivate Автоматически возвращает фокус на элемент, который был активен до активации ловушки.


Управление фокусом

Focus-trap отслеживает все фокусируемые элементы внутри контейнера. Под фокусируемыми понимаются:

  • <input>, <select>, <textarea>, <button>
  • <a> с атрибутом href
  • Элементы с tabindex="0"

Цикличность фокуса обеспечивается логикой, которая при попытке переместиться за последний элемент возвращает фокус на первый, и наоборот.

Пример принудительного перемещения фокуса:

focusTrap.activate();
document.querySelector('#modal input').focus();

Взаимодействие с динамическим контентом

Если внутри контейнера добавляются новые элементы после активации фокустрапа, библиотека корректно учитывает их при навигации с клавиатуры. Важно убедиться, что новые элементы имеют корректный tabindex и доступны для фокусировки.

Пример динамического обновления:

const newButton = document.createElement('button');
newButton.textContent = 'Новый элемент';
modal.appendChild(newButton);

После добавления нового элемента фокус автоматически будет включать его в циклическую навигацию.


Совместимость с React и другими фреймворками

Для интеграции с React часто используют обертки или хук useEffect:

import { useEffect, useRef } from 'react';
import { createFocusTrap } from 'focus-trap';

function Modal({ isOpen }) {
  const modalRef = useRef(null);
  const trapRef = useRef(null);

  useEffect(() => {
    trapRef.current = createFocusTrap(modalRef.current, { escapeDeactivates: true });
    if (isOpen) trapRef.current.activate();
    else trapRef.current.deactivate();
  }, [isOpen]);

  return <div ref={modalRef}>Контент модального окна</div>;
}

В Vue, Angular и других фреймворках используется аналогичный подход с жизненным циклом компонентов: инициализация в mounted/ngAfterViewInit, деактивация при уничтожении компонента.


Проблемы и подводные камни

  1. Фокус на несуществующий элемент – если initialFocus задан неверно, ловушка автоматически использует fallbackFocus.
  2. Неправильный tabindex – элементы с tabindex="-1" исключены из цикла фокуса.
  3. Модальные внутри модальных – вложенные ловушки требуют ручного управления активацией/деактивацией, чтобы избежать конфликтов.
  4. Скрытые элементы – элементы с display: none или visibility: hidden игнорируются.

Расширенные возможности

Focus-trap позволяет создавать асинхронные ловушки. Например, при открытии модального окна с задержкой или анимацией:

setTimeout(() => focusTrap.activate(), 300);

Можно комбинировать несколько ловушек с различными условиями активации, используя allowOutsideClick и кастомные функции проверки:

clickOutsideDeactivates: (event) => !event.target.closest('.always-active')

Такой подход позволяет гибко управлять доступностью интерфейса без нарушения UX.


Focus-trap является мощным инструментом для контроля фокуса в сложных интерфейсах. Корректная настройка initialFocus, fallbackFocus и обработчиков событий обеспечивает полную доступность и предсказуемое поведение клавиатурной навигации. Правильное использование предотвращает уход фокуса за пределы активного элемента и улучшает взаимодействие с приложением.