Ловушка фокуса

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

На вход функция tabbable() принимает DOM-элемент или документ, возвращая массив элементов, которые могут быть фокусированы. По умолчанию она учитывает:

  • стандартные интерактивные элементы (<a>, <button>, <input>, <textarea>, <select>)
  • элементы с атрибутом tabindex, где значение ≥ 0
  • элементы с атрибутом contenteditable="true"

Пример использования:

import { tabbable } from 'tabbable';

const container = document.querySelector('#modal');
const focusableElements = tabbable(container);

console.log(focusableElements);

Этот код вернёт все элементы внутри контейнера #modal, на которые пользователь может перейти с помощью клавиши Tab.


Атрибут tabindex и приоритет фокуса

Tabindex управляет порядком обхода элементов при навигации с клавиатуры:

  • tabindex="0" — элемент доступен для фокуса в естественном порядке документа.
  • tabindex="-1" — элемент исключён из обхода Tab, но его можно сфокусировать программно.
  • tabindex > 0 — элемент получает приоритет перед элементами с tabindex="0"; порядок определяется числом.

Tabbable корректно учитывает эти значения, возвращая элементы в порядке обхода, который соответствует поведению браузера.


Фильтрация скрытых и неактивных элементов

Tabbable игнорирует элементы, которые:

  • имеют display: none или visibility: hidden
  • находятся внутри элементов с aria-hidden="true"
  • отключены через атрибут disabled

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


Управление фокусом в модальных окнах

Одна из ключевых задач Tabbable — создание ловушек фокуса. Ловушка фокуса необходима, чтобы пользователь, открыв модальное окно, не мог покинуть его при помощи Tab до закрытия модалки.

Пример реализации ловушки фокуса:

const modal = document.getElementById('modal');
const focusableElements = tabbable(modal);
let firstElement = focusableElements[0];
let lastElement = focusableElements[focusableElements.length - 1];

modal.addEventListener('keydown', (event) => {
  if (event.key !== 'Tab') return;

  if (event.shiftKey) {
    if (document.activeElement === firstElement) {
      event.preventDefault();
      lastElement.focus();
    }
  } else {
    if (document.activeElement === lastElement) {
      event.preventDefault();
      firstElement.focus();
    }
  }
});

Ключевой момент: при обходе Tab и Shift+Tab происходит циклический перенос фокуса внутри модального окна.


Совместимость с динамически изменяемым содержимым

Tabbable подходит для работы с динамическими интерфейсами. Если внутри контейнера появляются новые интерактивные элементы, необходимо пересчитывать список фокусируемых элементов:

const refreshFocusableElements = () => {
  focusableElements = tabbable(modal);
  firstElement = focusableElements[0];
  lastElement = focusableElements[focusableElements.length - 1];
};

Вызывая refreshFocusableElements после изменения DOM, ловушка фокуса остаётся корректной.


Опции функции tabbable

Библиотека поддерживает дополнительные настройки:

  • getShadowRoot: возвращает shadow root для проверки элементов внутри теневых DOM.
  • includeContainer: если true, контейнер сам будет включён в список фокусируемых элементов, если он соответствует критериям.
  • tabindexIgnore: позволяет игнорировать элементы с определённым значением tabindex.

Пример с опцией:

const focusable = tabbable(modal, { includeContainer: true });

Взаимодействие с ARIA и доступностью

Tabbable учитывает ARIA-атрибуты для корректного обхода:

  • Элементы с aria-hidden="true" исключаются из списка.
  • Элементы, скрытые CSS, также игнорируются.
  • Можно комбинировать с role="dialog" для управления фокусом внутри модальных окон.

Это обеспечивает соответствие стандартам WCAG и упрощает работу с вспомогательными технологиями.


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

  1. Всегда пересчитывать фокусируемые элементы при изменении DOM внутри ловушки фокуса.
  2. Использовать tabindex="-1" для элементов, которые должны быть доступны только программно.
  3. При работе с модальными окнами или всплывающими панелями использовать циклическую фокусировку.
  4. Проверять совместимость с shadow DOM через getShadowRoot.
  5. Сочетать Tabbable с ARIA-атрибутами для доступности.

Эти практики обеспечивают корректное управление фокусом и улучшение взаимодействия для пользователей клавиатуры.