Сохранение состояния фокуса

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

Элемент считается tabbable, если он удовлетворяет следующим условиям:

  • имеет атрибут tabindex >= 0 или является стандартным интерактивным элементом (например, <button>, <input>, <a href="">).
  • не скрыт стилями (display: none, visibility: hidden) и не находится внутри скрытого контейнера.
  • не отключен (disabled для <button> или <input>).

Библиотека позволяет получить список всех таких элементов, а также управлять их состоянием программно.


Получение tabbable элементов

Для получения элементов используется функция:

import { tabbable } from 'tabbable';

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

tabbable(container) возвращает массив DOM-элементов, которые могут быть достигнуты с помощью клавиши Tab. Если контейнер не указан, функция ищет tabbable элементы в документе целиком.

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

  • Элементы возвращаются в порядке их естественной табуляции.
  • Можно использовать как с целыми формами, так и с частями страницы.
  • Возвращаемый массив можно фильтровать по атрибутам, классам или состояниям.

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

После получения tabbable элементов часто требуется программно переместить фокус. Для этого используется метод focus():

focusableElements[0].focus(); // фокус на первом элементе

Можно реализовать циклическую навигацию по элементам:

let index = 0;

document.addEventListener('keydown', (e) => {
  if (e.key === 'Tab') {
    e.preventDefault();
    index = (index + 1) % focusableElements.length;
    focusableElements[index].focus();
  }
});

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


Состояние фокуса и сохранение позиции

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

Пример сохранения фокуса:

let lastFocusedElement = document.activeElement;

const updateUI = () => {
  const container = document.querySelector('#form');
  // обновление DOM
  container.innerHTML = renderForm();

  // восстановление фокуса
  if (lastFocusedElement && container.contains(lastFocusedElement)) {
    lastFocusedElement.focus();
  } else {
    const firstFocusable = tabbable(container)[0];
    if (firstFocusable) firstFocusable.focus();
  }
};

document.addEventListener('focusin', (e) => {
  lastFocusedElement = e.target;
});

Особенности подхода:

  • Используется событие focusin для отслеживания текущего активного элемента.
  • После перерисовки DOM проверяется, доступен ли старый элемент. Если нет, фокус перемещается на первый tabbable элемент.
  • Можно расширять логику для восстановления фокуса на основе пользовательских приоритетов или состояний формы.

Работа с нестандартными элементами

Иногда интерфейс содержит элементы, которые не поддерживают фокус по умолчанию (<div> с обработкой событий). Для их включения в навигацию необходимо использовать tabindex:

<div tabindex="0">Кликабельный блок</div>
<div tabindex="1">Еще один блок</div>
  • tabindex="0" добавляет элемент в естественный порядок табуляции.
  • tabindex > 0 изменяет порядок вручную, что позволяет управлять приоритетами фокуса.
  • tabindex="-1" делает элемент фокусируемым только программно (element.focus()), исключая из стандартной навигации Tab.

Фильтрация и исключения

Библиотека позволяет фильтровать элементы по кастомным условиям. Например, исключение элементов с определённым классом:

const container = document.querySelector('#form');
const focusableElements = tabbable(container).filter(el => !el.classList.contains('skip'));

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


Интеграция с компонентами

Tabbable особенно полезен в крупных интерфейсах:

  • Модальные окна: ограничение фокуса внутри окна.
  • Меню и аккордеоны: фокус перемещается только по видимым элементам.
  • Динамические формы: сохранение позиции фокуса при изменении полей.
  • Списки и таблицы: управление фокусом по элементам с логикой сортировки или пагинации.

Программная навигация позволяет поддерживать полную доступность без вмешательства в нативное поведение браузера.


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

  • Всегда отслеживать document.activeElement для восстановления фокуса после изменений DOM.
  • Использовать tabindex для управления нестандартными элементами.
  • Применять фильтры для исключения элементов, которые не должны быть фокусируемыми в текущем состоянии интерфейса.
  • Для циклических фокусных зон реализовывать собственную логику с обработкой Tab и Shift+Tab.

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