Библиотека ally.js предназначена для упрощения работы с доступностью (accessibility) в веб-приложениях. Одним из ключевых аспектов доступного интерфейса является корректное управление фокусом при взаимодействии с динамическими элементами, такими как раскрывающиеся списки, модальные окна или всплывающие панели.
Фокус в браузере определяет, какой элемент активен для взаимодействия с клавиатурой. Некорректное управление фокусом приводит к проблемам с навигацией для пользователей, использующих клавиатуру или вспомогательные технологии.
Для работы с фокусом ally.js предоставляет API tabbable
и функции для установки фокуса на элементы. Пример инициализации:
import * as ally from 'ally.js';
const menuButton = document.querySelector('#menu-button');
const menuList = document.querySelector('#menu-list');
Здесь menuButton отвечает за открытие списка, а
menuList — за сам список.
При открытии списка важно переместить фокус на первый интерактивный элемент внутри него, чтобы клавиатурная навигация оставалась логичной.
menuButton.addEventListener('click', () => {
menuList.style.display = 'block';
const firstFocusable = ally.query.firstTabbable(menuList);
if (firstFocusable) {
firstFocusable.focus();
}
});
Ключевые моменты:
ally.query.firstTabbable(container) возвращает первый
элемент внутри контейнера, доступный для табуляции.focus() гарантирует, что
пользователь сразу может взаимодействовать с элементами списка.Для более сложных списков с динамическим контентом можно использовать
tabbable:
const focusableItems = ally.query.tabbable(menuList);
focusableItems[0].focus();
Закрытие списка должно возвращать фокус на элемент, который инициировал открытие. Это предотвращает потерю фокуса и улучшает навигацию.
function closeMenu() {
menuList.style.display = 'none';
menuButton.focus();
}
Если список закрывается автоматически (например, по клику вне элемента), важно отлавливать событие и корректно восстанавливать фокус:
document.addEventListener('click', (event) => {
if (!menuList.contains(event.target) && event.target !== menuButton) {
closeMenu();
}
});
Для более сложных сценариев, таких как модальные меню или панели, полезно ограничивать фокус внутри контейнера:
import { trapFocus } from 'ally.js';
const trap = trapFocus(menuList, {
context: document.body,
ignore: [menuButton]
});
Особенности работы trapFocus:
Tab и циклически
перемещает фокус внутри контейнера.Для отключения ограничения при закрытии списка используется метод
disengage():
trap.disengage();
Иногда список создается динамически, и необходимо проверять наличие интерактивных элементов перед установкой фокуса:
const focusableItems = ally.query.tabbable(menuList);
if (focusableItems.length > 0) {
focusableItems[0].focus();
} else {
menuButton.focus();
}
Это предотвращает ошибки, если список пуст или содержит элементы, недоступные для табуляции.
Для удобной навигации внутри списка рекомендуется обрабатывать
клавиши Escape и Arrow:
menuList.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
closeMenu();
}
});
Использование ally.js в сочетании с управлением фокусом позволяет создавать доступные интерфейсы без необходимости вручную проверять все элементы на доступность.
ally.query.tabbable.menuitem, необходимо правильно задавать атрибут
tabindex и роли ARIA, чтобы функции ally.js корректно
определяли доступные элементы.