Библиотека 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-атрибуты:
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 помогает определить следующие шаги по элементам:
Пример обработки стрелок в меню:
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, управлять фокусом и обеспечивать полноценную клавиатурную навигацию.