React

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


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

Для использования Focus-trap в проекте React рекомендуется установить библиотеку через npm:

npm install focus-trap

или через Yarn:

yarn add focus-trap

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

import { createFocusTrap } from 'focus-trap';

Метод createFocusTrap создаёт объект фокус-трап, который управляет поведением фокуса внутри заданного DOM-элемента.


Создание базового фокус-трапа

Для базовой реализации создаётся контейнер, например модальное окно, и привязывается к нему фокус-трап:

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

function Modal({ isOpen, onClose }) {
  const modalRef = useRef(null);
  const focusTrapRef = useRef(null);

  useEffect(() => {
    if (!focusTrapRef.current) {
      focusTrapRef.current = createFocusTrap(modalRef.current, {
        escapeDeactivates: true,
        clickOutsideDeactivates: true,
        allowOutsideClick: true,
      });
    }

    if (isOpen) {
      focusTrapRef.current.activate();
    } else {
      focusTrapRef.current.deactivate();
    }

    return () => focusTrapRef.current.deactivate();
  }, [isOpen]);

  return (
    <div
      ref={modalRef}
      style={{
        display: isOpen ? 'block' : 'none',
        position: 'fixed',
        top: 0,
        left: 0,
        width: '100%',
        height: '100%',
        backgroundColor: 'rgba(0,0,0,0.5)',
      }}
    >
      <div style={{ background: '#fff', margin: '100px auto', padding: '20px', width: '300px' }}>
        <button onCl ick={onClose}>Закрыть</button>
        <input placeholder="Введите текст" />
        <button>Подтвердить</button>
      </div>
    </div>
  );
}

export default Modal;

Ключевые моменты:

  • escapeDeactivates позволяет закрывать трап клавишей Escape.
  • clickOutsideDeactivates отключает фокус-трап при клике вне контейнера.
  • allowOutsideClick разрешает кликать вне контейнера, не деактивируя трап сразу, если нужно дополнительное управление.

Настройка фокуса при активации

Focus-trap поддерживает опцию initialFocus, которая задаёт элемент, который получит фокус при активации трапа:

createFocusTrap(modalRef.current, {
  initialFocus: '#firstInput',
});

Это особенно важно для модальных окон с формами, где нужно, чтобы пользователь сразу мог вводить данные без дополнительного клика.


Поддержка динамических элементов

Для интерфейсов с динамически добавляемыми элементами (например, асинхронная загрузка контента) можно использовать returnFocusOnDeactivate и onActivate:

const trap = createFocusTrap(modalRef.current, {
  onActivate: () => console.log('Трап активирован'),
  onDeactivate: () => console.log('Трап деактивирован'),
  returnFocusOnDeactivate: true,
});
  • onActivate вызывается при активации трапа, можно использовать для настройки состояния компонента.
  • returnFocusOnDeactivate возвращает фокус на элемент, который был активен до включения трапа, что повышает доступность.

Управление событиями клавиатуры

Focus-trap по умолчанию перехватывает клавишу Tab для циклического перемещения фокуса внутри контейнера. Для расширенной кастомизации можно использовать:

createFocusTrap(modalRef.current, {
  tabbableOptions: {
    displayCheck: 'none', // игнорирует проверку видимости
  },
});

Это позволяет корректно работать с элементами, которые временно скрываются или становятся неактивными.


Взаимодействие с React-порталами

Для модальных окон, рендерящихся через ReactDOM.createPortal, важно передавать реальный DOM-узел в createFocusTrap, так как библиотека работает напрямую с DOM. Фокус-трап будет корректно работать даже при переносе модалки вне основного DOM-дерева.

import ReactDOM from 'react-dom';

return ReactDOM.createPortal(
  <div ref={modalRef}>...</div>,
  document.body
);

Управление множественными трапами

Если на странице одновременно может существовать несколько фокус-трапов, рекомендуется управлять их активацией вручную:

const firstTrap = createFocusTrap(firstRef.current);
const secondTrap = createFocusTrap(secondRef.current);

firstTrap.activate();
secondTrap.deactivate();

Это предотвращает конфликт фокуса и гарантирует, что активен только один трап в момент времени.


Доступность и ARIA

Для максимальной доступности важно:

  • Помечать модальные окна атрибутом role="dialog".
  • Использовать aria-modal="true".
  • Связывать заголовок с помощью aria-labelledby для экранных читалок.

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


Интеграция с хуками React

Для удобства можно создать кастомный хук:

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

function useFocusTrap(isActive) {
  const ref = useRef(null);
  const trapRef = useRef(null);

  useEffect(() => {
    if (!trapRef.current && ref.current) {
      trapRef.current = createFocusTrap(ref.current);
    }

    if (isActive) {
      trapRef.current.activate();
    } else {
      trapRef.current.deactivate();
    }

    return () => trapRef.current.deactivate();
  }, [isActive]);

  return ref;
}

Применение:

const modalRef = useFocusTrap(isModalOpen);
return <div ref={modalRef}>...</div>;

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


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