Доступная навигация по меню

Библиотека ally.js предназначена для упрощения реализации доступной навигации в веб-приложениях. Основная задача — управление фокусом, обеспечение правильного взаимодействия с клавиатурой и совместимость с технологиями ассистивного доступа.

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

Фокус в DOM — ключевой элемент доступности. ally.js предоставляет методы для его проверки, установки и перемещения:

  • ally.query.firstTabbable() — возвращает первый элемент, доступный для табуляции.
  • ally.query.lastTabbable() — возвращает последний табулябельный элемент внутри контейнера.
  • ally.query.nextTabbable(element) — получает следующий элемент для табуляции относительно текущего.
  • ally.query.previousTabbable(element) — аналогично, для предыдущего.

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

Пример циклического фокуса:

const menu = document.querySelector('.menu');
const firstItem = ally.query.firstTabbable({ context: menu });
const lastItem = ally.query.lastTabbable({ context: menu });

menu.addEventListener('keydown', (e) => {
  if (e.key === 'Tab') {
    if (e.shiftKey && document.activeElement === firstItem) {
      e.preventDefault();
      lastItem.focus();
    } else if (!e.shiftKey && document.activeElement === lastItem) {
      e.preventDefault();
      firstItem.focus();
    }
  }
});

Контроль видимости и доступности элементов

Ally.js позволяет проверять, доступен ли элемент для взаимодействия:

  • ally.is.focusable(element) — возвращает true, если элемент можно сфокусировать.
  • ally.is.tabbable(element) — проверяет, может ли элемент быть частью порядка табуляции.
  • ally.is.visible(element) — проверяет, видим ли элемент на странице.

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

Поддержка ARIA и управление ролями

Для корректной работы ассистивных технологий важно правильно использовать ARIA-атрибуты:

  • role="menu" и role="menuitem" для списков навигации.
  • aria-haspopup="true" для кнопок, открывающих подменю.
  • aria-expanded для отражения состояния видимости подменю.

Ally.js не изменяет роли, но обеспечивает корректное управление фокусом и клавиатурной навигацией, что делает работу ARIA эффективной.

Пример открытия подменю с обновлением атрибутов:

const button = document.querySelector('.menu-button');
const submenu = document.querySelector('.submenu');

button.addEventListener('click', () => {
  const expanded = button.getAttribute('aria-expanded') === 'true';
  button.setAttribute('aria-expanded', String(!expanded));
  if (!expanded) {
    const firstSubItem = ally.query.firstTabbable({ context: submenu });
    firstSubItem.focus();
  }
});

Навигация с клавиатуры

Клавиатурная навигация критична для доступного меню. Ally.js помогает определить следующие шаги по элементам:

  • Использование стрелок вверх/вниз для вертикального меню.
  • Использование стрелок влево/вправо для горизонтального меню.
  • Tab и Shift+Tab для выхода из меню или перехода между секциями.

Пример обработки стрелок в меню:

menu.addEventListener('keydown', (e) => {
  const items = ally.query.tabbable({ context: menu });
  let index = items.indexOf(document.activeElement);
  
  if (e.key === 'ArrowDown') {
    e.preventDefault();
    const nextIndex = (index + 1) % items.length;
    items[nextIndex].focus();
  } else if (e.key === 'ArrowUp') {
    e.preventDefault();
    const prevIndex = (index - 1 + items.length) % items.length;
    items[prevIndex].focus();
  }
});

Управление модальными и контекстными меню

Модальные окна и выпадающие меню требуют блокировки фокуса вне контейнера. Ally.js предоставляет методы для ограничения фокуса внутри определённого элемента:

  • ally.maintain.focus() — позволяет держать фокус внутри контейнера, автоматически возвращая его к последнему активному элементу.
  • ally.maintain.disabled() — временно делает элементы вне контейнера недоступными для фокуса.

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

const modal = document.querySelector('.modal');
const stopFocus = ally.maintain.focus({ context: modal });

// При закрытии модального окна
stopFocus.disengage();

Взаимодействие с динамическими элементами

Ally.js корректно работает с элементами, которые создаются или удаляются динамически. Методы query.* позволяют получать актуальный список tabbable-элементов в реальном времени, что особенно важно для SPA и интерактивных интерфейсов.

Использование ally.js значительно повышает доступность сложных меню, позволяя реализовать стандарты WAI-ARIA, управлять фокусом и обеспечивать полноценную клавиатурную навигацию.