Принудительное включение элементов в trap

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

Иногда стандартного поведения библиотеки недостаточно: не все интерактивные элементы можно автоматически «поймать». В таких случаях используется возможность принудительного включения элементов в trap, что позволяет гарантировать доступность специфических кнопок, ссылок или кастомных элементов управления.


Основы работы с focusTrapOptions

При инициализации trap через createFocusTrap(container, options) можно передать объект options, содержащий ключевые параметры:

import { createFocusTrap } from 'focus-trap';

const trap = createFocusTrap('#modal', {
  onActivate: () => console.log('Trap активирован'),
  onDeactivate: () => console.log('Trap деактивирован'),
  clickOutsideDeactivates: true,
  tabbableOptions: {
    includeFallback: true
  }
});

Важной частью для принудительного включения является опция allowOutsideClick и tabbableOptions. Однако для явного контроля элементов используется метод setInitialFocus и опция fallbackFocus совместно с фильтрацией элементов через tabbableOptions.


Принудительное включение через tabbableOptions.include

Библиотека Focus-trap интегрирует модуль tabbable, который определяет интерактивные элементы. В tabbableOptions можно указать селекторы или функции для расширения списка «ловимых» элементов.

const trap = createFocusTrap('#modal', {
  tabbableOptions: {
    include: ['.custom-button', '#special-link']
  }
});

Здесь .custom-button и #special-link будут всегда включены в список элементов, на которые можно перейти клавишей Tab, даже если стандартная фильтрация их не учитывает.

Особенности работы include:

  • Принимает массив селекторов CSS или функцию, возвращающую массив элементов.
  • Элементы включаются поверх стандартных tabbable-элементов.
  • Полезно для динамически создаваемых кнопок или элементов с нестандартными атрибутами (например, role="button" без tabindex).

Использование функции для динамического включения

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

const trap = createFocusTrap('#modal', {
  tabbableOptions: {
    include: () => Array.from(document.querySelectorAll('.dynamic-focus'))
  }
});

Каждый раз при активации trap функция вызывается, возвращая актуальный список элементов. Это позволяет управлять фокусом даже в сложных интерфейсах с асинхронной подгрузкой контента.


Принудительный фокус на конкретный элемент

Для активации trap с фокусом на заранее выбранном элементе применяется опция setInitialFocus:

const trap = createFocusTrap('#modal', {
  setInitialFocus: '#first-input'
});

Даже если #first-input обычно не tabbable, он будет включен в trap и получит фокус при активации.

Аналогично работает fallbackFocus — элемент, на который фокус перейдёт, если tabbable-элементы отсутствуют:

fallbackFocus: '#default-focus'

Сценарии применения принудительного включения

  1. Кастомные кнопки и виджеты: элементы с нестандартными атрибутами или без tabindex=0.
  2. Динамически добавляемый контент: формы, списки или всплывающие элементы, которые появляются после рендеринга.
  3. Обход ограничений стандартного tabbable: например, role="link" без тега <a>.
  4. Контроль фокуса при сложных модальных структурах: несколько уровней вложенных модальных окон, где часть элементов должна оставаться интерактивной.

Практический пример

import { createFocusTrap } from 'focus-trap';

const modalTrap = createFocusTrap('#modal', {
  onActivate: () => console.log('Modal trap активирован'),
  setInitialFocus: () => document.querySelector('#important-button'),
  tabbableOptions: {
    include: ['.always-focus', (container) => container.querySelectorAll('[data-dynamic-focus]')]
  },
  clickOutsideDeactivates: true
});

// Активация trap
document.querySelector('#open-modal').addEventListener('click', () => modalTrap.activate());

В этом примере:

  • #important-button получает фокус при открытии модалки.
  • Все элементы с классом .always-focus и атрибутом data-dynamic-focus включаются в список tabbable.
  • Trap деактивируется при клике вне модального окна.

Важные моменты

  • Не стоит включать лишние элементы: фокус должен оставаться логически внутри контейнера.
  • Проверка на доступность: принудительно включенные элементы должны быть видимы и интерактивны.
  • Динамические include-функции вызываются при каждом пересчёте tabbable-элементов, что обеспечивает корректность при изменении DOM.

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