aria-modal

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

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

Для создания нового фокус-трапа используется функция createFocusTrap, которая принимает два аргумента: селектор или DOM-элемент контейнера и объект опций:

import { createFocusTrap } from 'focus-trap';

const modal = document.querySelector('#modal');
const trap = createFocusTrap(modal, {
  escapeDeactivates: true,
  clickOutsideDeactivates: true,
  initialFocus: '#modal input:first-of-type'
});

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

  • escapeDeactivates — закрывает фокус-трап при нажатии клавиши Escape.
  • clickOutsideDeactivates — позволяет деактивировать фокус-трап при клике вне контейнера.
  • initialFocus — задаёт элемент, который получит фокус при активации трапа.

Активация и деактивация

Фокус-трап активируется методом activate() и деактивируется методом deactivate():

trap.activate();
// Фокус теперь ограничен контейнером #modal

trap.deactivate();
// Фокус возвращается к элементу, который был активен до активации

Если требуется автоматический возврат фокуса к триггеру открытия модального окна, это реализуется через опцию returnFocusOnDeactivate:

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

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

Focus-trap автоматически определяет все элементы внутри контейнера, на которые можно поставить фокус (tabindex ≥ 0, a[href], button, input, select, textarea). Дополнительно можно контролировать поведение через опции:

  • allowOutsideClick — функция или булево значение, позволяющее клики вне контейнера, не деактивируя трап.
  • fallbackFocus — элемент для фокусировки, если initialFocus не найден.
const trap = createFocusTrap(modal, {
  fallbackFocus: modal,
  allowOutsideClick: (event) => event.target.classList.contains('allow-click')
});

Использование с модальными окнами

В контексте модальных окон и aria-modal правильная организация фокуса критична для доступности. Важно, чтобы:

  1. При открытии модального окна фокус сразу попадал на первый интерактивный элемент.
  2. Пользователь не мог выйти за пределы модального окна с помощью клавиши Tab.
  3. При закрытии модального окна фокус возвращался к элементу, который инициировал открытие.

Пример интеграции с кнопкой открытия:

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

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

const closeButton = modal.querySelector('.close');
closeButton.addEventListener('click', () => {
  trap.deactivate();
  modal.removeAttribute('aria-modal');
  modal.style.display = 'none';
});

Асинхронные сценарии

Если элементы модального окна подгружаются динамически, initialFocus может быть недоступен на момент активации. Для этого используется метод trap.activate() с опцией delay или ручной фокус после рендеринга:

trap.activate();
setTimeout(() => {
  const input = modal.querySelector('input:first-of-type');
  if(input) input.focus();
}, 0);

Комплексные опции и события

Focus-trap поддерживает ряд событий и обратных вызовов:

  • onActivate — вызывается при активации трапа.
  • onDeactivate — вызывается при деактивации.
  • checkCanFocusTrap — функция, возвращающая Promise, позволяющая откладывать активацию до готовности элементов.

Пример с асинхронной загрузкой:

const trap = createFocusTrap(modal, {
  checkCanFocusTrap: () => {
    return new Promise(resolve => {
      setTimeout(resolve, 100); // дождаться рендера элементов
    });
  },
  onActivate: () => console.log('Focus trap activated'),
  onDeactivate: () => console.log('Focus trap deactivated')
});

Совместимость с ARIA

Для корректного использования с aria-modal важно:

  • Добавлять aria-modal="true" к контейнеру при активации.
  • Устанавливать role="dialog" или role="alertdialog" для модального окна.
  • Скринридеры будут понимать, что интерактивная область ограничена контейнером.

Focus-trap не управляет ARIA-атрибутами автоматически, поэтому это нужно делать вручную в обработчиках активации и деактивации.

Практические рекомендации

  • Всегда задавать fallbackFocus, чтобы избежать ошибок при динамическом контенте.
  • Использовать returnFocusOnDeactivate, чтобы не терять контекст пользователя.
  • Для сложных интерфейсов с вложенными модальными окнами можно создавать несколько фокус-трапов и управлять их активацией последовательно.
  • Проверять работу с клавишами Tab и Shift+Tab, чтобы убедиться в циклическом обходе элементов.

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