Навигация стрелками

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

Focus-trap создаёт «ловушку фокуса», которая:

  • блокирует клавишу Tab и Shift+Tab внутри контейнера;
  • обеспечивает циклическое перемещение между интерактивными элементами;
  • предотвращает потерю фокуса при открытых модальных окнах.

Создание и инициализация ловушки

Для работы с библиотекой необходим элемент DOM, внутри которого будет ограничен фокус. Основной метод — createFocusTrap:

import { createFocusTrap } from 'focus-trap';

const modal = document.getElementById('modal');
const focusTrap = createFocusTrap(modal, {
  escapeDeactivates: true,
  clickOutsideDeactivates: true,
  allowOutsideClick: true
});

Параметры:

  • escapeDeactivates — позволяет закрывать ловушку клавишей Escape.
  • clickOutsideDeactivates — деактивация при клике за пределами контейнера.
  • allowOutsideClick — разрешает обработку кликов за пределами контейнера без выхода из ловушки.

Активация и деактивация осуществляется методами:

focusTrap.activate();
focusTrap.deactivate();

Перемещение фокуса с помощью стрелок

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

Для этого требуется:

  1. Настроить слушатель событий клавиатуры на контейнере.
  2. Вычислять текущий элемент с фокусом.
  3. Определять следующий элемент в направлении стрелки.
  4. Перемещать фокус с помощью element.focus().

Пример для горизонтального меню:

const menuItems = Array.from(modal.querySelectorAll('button, [tabindex]:not([tabindex="-1"])'));

modal.addEventListener('keydown', (e) => {
  const index = menuItems.indexOf(document.activeElement);
  if (e.key === 'ArrowRight') {
    e.preventDefault();
    const nextIndex = (index + 1) % menuItems.length;
    menuItems[nextIndex].focus();
  } else if (e.key === 'ArrowLeft') {
    e.preventDefault();
    const prevIndex = (index - 1 + menuItems.length) % menuItems.length;
    menuItems[prevIndex].focus();
  }
});

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

  • Массив menuItems должен содержать только интерактивные элементы, доступные для фокуса.
  • При достижении конца массива фокус возвращается к первому элементу, создавая циклическую навигацию.
  • Использование preventDefault() предотвращает стандартное поведение браузера и обеспечивает предсказуемость навигации.

Комбинирование Tab и стрелок

В сложных интерфейсах с сеткой (например, таблицы, галереи) удобно комбинировать Tab и стрелки:

modal.addEventListener('keydown', (e) => {
  const index = gridItems.indexOf(document.activeElement);
  if (e.key === 'ArrowDown') {
    e.preventDefault();
    const nextIndex = (index + columns) % gridItems.length;
    gridItems[nextIndex].focus();
  } else if (e.key === 'ArrowUp') {
    e.preventDefault();
    const prevIndex = (index - columns + gridItems.length) % gridItems.length;
    gridItems[prevIndex].focus();
  }
});
  • columns — количество элементов в одной строке сетки.
  • Логика позволяет перемещать фокус вертикально и горизонтально, сохраняя замкнутый цикл.

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

Focus-trap поддерживает хуки для дополнительного контроля:

  • onActivate — вызывается при активации ловушки.
  • onDeactivate — вызывается при деактивации.
  • checkCanFocusTrap — асинхронная проверка перед активацией.

Пример:

const focusTrap = createFocusTrap(modal, {
  onActivate: () => console.log('Ловушка активирована'),
  onDeactivate: () => console.log('Ловушка деактивирована'),
  checkCanFocusTrap: () => new Promise(resolve => {
    setTimeout(() => resolve(true), 100);
  })
});

Рекомендации по использованию

  • Всегда включать только одну активную ловушку на странице. Одновременное использование нескольких может вызвать конфликты.
  • Все интерактивные элементы внутри ловушки должны быть доступны через Tab и стрелки.
  • Для сложных интерфейсов рекомендуется создавать массивы элементов с фокусом один раз и обновлять их при динамическом изменении DOM.

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