Библиотека 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', // игнорирует проверку видимости
},
});
Это позволяет корректно работать с элементами, которые временно скрываются или становятся неактивными.
Для модальных окон, рендерящихся через
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();
Это предотвращает конфликт фокуса и гарантирует, что активен только один трап в момент времени.
Для максимальной доступности важно:
role="dialog".aria-modal="true".aria-labelledby для
экранных читалок.Focus-trap обеспечивает корректное перемещение фокуса, но атрибуты ARIA делают взаимодействие полностью доступным.
Для удобства можно создать кастомный хук:
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, позволяя корректно контролировать фокус, предотвращать потерю контекста и обеспечивать комфортную навигацию с клавиатуры.