Что такое focus-trap и зачем он нужен

focus-trap — это библиотека для управления фокусом клавиатуры внутри определённой области DOM. Она позволяет ограничивать перемещение фокуса пользователем в пределах заданного контейнера, что особенно важно для модальных окон, всплывающих панелей, диалогов и других интерактивных элементов интерфейса.

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


Установка и подключение

Библиотека доступна через npm и Yarn:

npm install focus-trap

или

yarn add focus-trap

Для использования в браузере можно подключить через CDN:

<script src="https://unpkg.com/focus-trap/dist/focus-trap.min.js"></script>

После подключения создаётся объект trap с указанием контейнера, в котором необходимо зафиксировать фокус.


Инициализация focus-trap

Базовый синтаксис создания фокус-трэпа:

import { createFocusTrap } from 'focus-trap';

const modal = document.querySelector('#modal');

const trap = createFocusTrap(modal, {
  escapeDeactivates: true,
  clickOutsideDeactivates: true,
  fallbackFocus: modal
});

Ключевые параметры:

  • escapeDeactivates — при нажатии клавиши Escape фокус-трэп отключается.
  • clickOutsideDeactivates — клик вне контейнера выводит фокус из трэпа.
  • fallbackFocus — элемент, на который будет установлен фокус, если внутри контейнера нет подходящих элементов для фокуса.

Для активации:

trap.activate();

Для деактивации:

trap.deactivate();

Особенности работы с модальными окнами

Focus-trap наиболее часто используется с модальными окнами. Основная задача — удерживать фокус внутри модального окна до момента его закрытия. Это предотвращает потерю контекста и обеспечивает корректную работу клавиатурной навигации.

Пример управления модальным окном:

const openButton = document.querySelector('#openModal');
const closeButton = document.querySelector('#closeModal');
const modal = document.querySelector('#modal');

const trap = createFocusTrap(modal, {
  escapeDeactivates: true,
  clickOutsideDeactivates: true
});

openButton.addEventListener('click', () => {
  modal.style.display = 'block';
  trap.activate();
});

closeButton.addEventListener('click', () => {
  trap.deactivate();
  modal.style.display = 'none';
});

Настройка поведения

Focus-trap позволяет тонко настраивать поведение через дополнительные опции:

  • initialFocus — элемент, который получает фокус при активации трэпа.
  • allowOutsideClick — позволяет кликам по элементам вне контейнера взаимодействовать с ними, не деактивируя трэп.
  • returnFocusOnDeactivate — возвращает фокус на элемент, который был активен до активации трэпа.
  • tabbableOptions — конфигурация библиотеки tabbable, используемой внутри focus-trap для определения элементов, доступных для фокуса.

Пример с initialFocus:

const trap = createFocusTrap(modal, {
  initialFocus: '#modalInput',
  escapeDeactivates: true
});

Продвинутое использование

Focus-trap можно интегрировать с асинхронными действиями и динамически создаваемыми элементами. При этом важно вызывать updateTabbableNodes() после изменения DOM, чтобы трэп корректно определял доступные элементы для фокуса:

trap.updateTabbableNodes();

Также можно комбинировать несколько трэпов, управляя их активацией и деактивацией, например, при вложенных модальных окнах:

const parentTrap = createFocusTrap(parentModal);
const childTrap = createFocusTrap(childModal);

parentTrap.activate();
childTrap.activate();
childTrap.deactivate(); // возвращает фокус в parentTrap

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

  1. Элементы без tabindex — focus-trap фокусируется только на tabbable-элементах. Если в контейнере нет интерактивных элементов, необходимо задать tabindex="0" для fallback-фокуса.
  2. Асинхронные модификации DOM — при добавлении новых элементов после активации трэпа нужно вызвать updateTabbableNodes().
  3. Множественные трэпы — следует внимательно управлять приоритетами, чтобы фокус не «прыгал» между слоями интерфейса.

Интеграция с фреймворками

Focus-trap легко интегрируется с популярными фреймворками:

  • React — через focus-trap-react, оборачивая модальные компоненты.
  • Vue — можно использовать напрямую с v-if или v-show, создавая трэп при монтировании компонента.
  • Angular — через сервисы и жизненный цикл компонента, активируя трэп при открытии модального окна.

Рекомендации по accessibility

Focus-trap усиливает доступность интерфейсов, но для полной compliance важно:

  • Указывать aria-modal="true" для модальных окон.
  • Использовать aria-labelledby и aria-describedby.
  • Обеспечивать логичную последовательность фокуса при навигации с клавиатуры.

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