Ограничение фокуса внутри контейнера

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


Основное назначение ally.query.tabbable

ally.query.tabbable используется для:

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

Модуль возвращает массив DOM-элементов, которые соответствуют критериям tabbable:

  1. Элемент может быть фокусирован.
  2. Элемент видим пользователю (display !== none, visibility !== hidden, отсутствие aria-hidden="true" на родителях).
  3. Элемент не отключен (disabled не установлен).

Использование функции ally.query.tabbable

Базовый синтаксис:

import tabbable from 'ally.js/query/tabbable';

const elements = tabbable({
  context: document.body,
  includeContext: false,
});

Параметры:

  • context (DOM-элемент, по умолчанию document) Определяет корневой узел, внутри которого происходит поиск tabbable-элементов.

  • includeContext (boolean, по умолчанию false) Если true, корневой элемент включается в результат, если он сам tabbable.

  • filter (функция, необязательная) Позволяет дополнительно фильтровать результаты. Возвращает true, если элемент должен остаться.

Пример с фильтром:

const interactiveButtons = tabbable({
  context: document.querySelector('#container'),
  filter: (element) => element.tagName === 'BUTTON'
});

Критерии tabbable-элементов

Элементы, которые считаются tabbable:

  • <a> с атрибутом href.
  • <button>, <input>, <select>, <textarea> (если не disabled).
  • Любые элементы с tabindex="0" или положительным tabindex.
  • Элементы с contenteditable="true".

Элементы исключаются из списка, если:

  • Имеют display: none или visibility: hidden.
  • На них установлено aria-hidden="true" через родительский элемент.
  • Установлен атрибут disabled.
  • tabindex равен отрицательному значению (например, tabindex="-1").

Работа с tabindex

ally.query.tabbable корректно обрабатывает положительные, нулевые и отрицательные значения tabindex:

  • tabindex="0": элемент tabbable, следует естественному порядку DOM.
  • tabindex > 0: элемент tabbable, приоритет фокусировки выше стандартного порядка.
  • tabindex < 0: элемент не tabbable, но может быть программно сфокусирован через JS.

Оптимизация производительности

При работе с большим DOM-деревом рекомендуется:

  • Ограничивать область поиска через параметр context.
  • Использовать filter для сокращения количества проверяемых элементов.
  • Избегать частых вызовов функции на всей странице при динамических изменениях DOM.

Пример эффективного поиска:

const visibleInputs = tabbable({
  context: document.querySelector('.form-section'),
  filter: (el) => el.tagName === 'INPUT' && el.offsetParent !== null
});

Практические сценарии применения

  1. Модальные окна При открытии модального окна важно ограничить tabbable-элементы только внутри окна, чтобы Tab не переходил на элементы основного контента.
const modalTabbable = tabbable({ context: modalElement });
modalElement.addEventListener('keydown', (event) => {
  if (event.key === 'Tab') {
    // логика циклического обхода tabbable-элементов
  }
});
  1. Динамически добавляемые элементы После добавления новых интерактивных элементов нужно повторно вызывать tabbable, чтобы обновить список доступных элементов.

  2. Пользовательские виджеты Создание кастомных селектов, вкладок и аккордеонов требует управления фокусом. Использование ally.query.tabbable позволяет корректно определить порядок перехода по клавише Tab.


Совместимость и ограничения

  • Поддержка всех современных браузеров, включая Chrome, Firefox, Safari, Edge.
  • Не учитывает элементы, скрытые за opacity: 0 или за пределами видимой области; учитывает только видимость через CSS и ARIA.
  • Не изменяет DOM и не устанавливает фокус — возвращает только список элементов.

Взаимодействие с другими модулями ally.js

  • ally.query.focusable: возвращает элементы, которые могут быть сфокусированы программно. Различие: tabbable учитывает только элементы, доступные через Tab.
  • ally.maintain.tabFocus: помогает удерживать фокус внутри контейнера, используя результаты tabbable.
  • ally.style.focusVisible: стилизует элементы при получении фокуса, совместимо с tabbable-элементами.

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

  • Всегда использовать context для ограничения области поиска и повышения производительности.
  • Применять filter для специфических интерактивных элементов.
  • Использовать вместе с модульными методами управления фокусом для реализации доступных интерфейсов.
  • Проверять результат в разных браузерах и с включенными технологиями доступности (например, экранными читалками).

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