Клавиатурная навигация по вариантам

Библиотека ally.js предоставляет комплексный набор инструментов для работы с доступностью веб-приложений, включая управление фокусом, определение видимых элементов и создание удобной клавиатурной навигации. В контексте навигации по вариантам, основное внимание уделяется управлению фокусом, предотвращению «залипания» клавиатурного взаимодействия и поддержке ARIA-атрибутов.

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


Навигация между интерактивными элементами

Для работы с клавиатурной навигацией ally.js предоставляет метод tabOrder. Он позволяет определить последовательность обхода фокуса по DOM-элементам:

const tabSequence = ally.query.tabOrder({
  context: document.getElementById('options-container')
});

tabSequence.forEach(element => {
  element.addEventListener('focus', () => {
    console.log('Фокус установлен на элемент:', element);
  });
});

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

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

Управление фокусом с помощью ally.js

Методы установки и перемещения фокуса

Ally.js предоставляет несколько функций для точного управления фокусом:

  • focusable – определяет, какие элементы могут получать фокус.
  • focus – программно устанавливает фокус на конкретный элемент.
  • nextFocus / previousFocus – позволяет переходить к следующему или предыдущему фокусируемому элементу с учётом видимости и доступности.

Пример установки фокуса на первый видимый вариант:

const options = ally.query.focusable({
  context: document.getElementById('options-container')
});

if (options.length) {
  ally.focus.focus(options[0]);
}

Особенности:

  • Игнорируются элементы с display: none, visibility: hidden и aria-hidden="true".
  • Можно комбинировать с кастомной логикой сортировки элементов.

Реализация стрелочной навигации

Навигация по вариантам часто требует использования стрелок клавиатуры (Arrow Up, Arrow Down, Arrow Left, Arrow Right). Ally.js не предоставляет готовых обработчиков для конкретных клавиш, но облегчает работу с фокусом, что позволяет реализовать следующую схему:

const optionsContainer = document.getElementById('options-container');
const options = Array.from(ally.query.focusable({ context: optionsContainer }));

optionsContainer.addEventListener('keydown', (event) => {
  const currentIndex = options.indexOf(document.activeElement);
  
  switch(event.key) {
    case 'ArrowDown':
      const nextIndex = (currentIndex + 1) % options.length;
      ally.focus.focus(options[nextIndex]);
      event.preventDefault();
      break;
    case 'ArrowUp':
      const prevIndex = (currentIndex - 1 + options.length) % options.length;
      ally.focus.focus(options[prevIndex]);
      event.preventDefault();
      break;
  }
});

Принципы реализации:

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

Поддержка ARIA-атрибутов

Для компонентов с вариантами (например, <ul role="listbox"> или <div role="radiogroup">) важно синхронизировать фокус с ARIA-состояниями:

  • aria-selected для активного варианта.
  • aria-activedescendant для контейнера, когда фокус перемещается программно.

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

options.forEach((option) => {
  option.addEventListener('focus', () => {
    options.forEach(o => o.setAttribute('aria-selected', 'false'));
    option.setAttribute('aria-selected', 'true');
    optionsContainer.setAttribute('aria-activedescendant', option.id);
  });
});

Вывод: совместное использование ally.js и ARIA обеспечивает корректное восприятие навигации скринридерами и другими вспомогательными технологиями.


Динамическое добавление и удаление вариантов

В интерфейсах с изменяемым набором вариантов необходимо обновлять список фокусируемых элементов:

function refreshOptions() {
  return ally.query.focusable({ context: optionsContainer });
}

let currentOptions = refreshOptions();

optionsContainer.addEventListener('DOMNodeInserted', () => {
  currentOptions = refreshOptions();
});

optionsContainer.addEventListener('DOMNodeRemoved', () => {
  currentOptions = refreshOptions();
});

Особенности:

  • Ally.js автоматически фильтрует невидимые элементы.
  • Обновление массива элементов предотвращает потерю фокуса при изменении DOM.
  • Поддержка событий DOM помогает поддерживать корректную клавиатурную навигацию в динамических списках.

Дополнительные возможности

  • tabbable – возвращает элементы, которые реально доступны через клавишу Tab, исключая элементы с tabindex="-1".
  • is.focused – проверяет, находится ли элемент в фокусе, полезно для условных переходов.
  • nextTabbable / previousTabbable – позволяет реализовать кастомные циклические или направленные переходы по фокусируемым элементам.

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


Клавиатурная навигация по вариантам с использованием ally.js сочетает в себе программное управление фокусом, фильтрацию невидимых элементов, интеграцию с ARIA и возможность динамического обновления интерфейса. Такой подход позволяет создавать доступные и интуитивно понятные компоненты, соответствующие стандартам WCAG и рекомендациям по доступности веб-контента.