Библиотека ally.js предоставляет мощный набор
инструментов для управления доступностью (a11y) в веб-приложениях. Одной
из ключевых задач является работа с кастомными контролами, которые не
являются стандартными элементами формы (<input>,
<button>, <select>), но требуют
полной доступности для пользователей с ассистивными технологиями.
Кастомные контролы могут включать: переключатели (switch), слайдеры,
вкладки (tabs), модальные окна с кнопками управления и другие
интерактивные элементы, созданные с использованием div,
span или других блочных элементов. В таких случаях
необходимо самостоятельно управлять фокусом, состоянием и
взаимодействием с клавиатурой.
ally.js предоставляет функции для управления фокусом и навигацией:
ally.query.focusable({ context }) — возвращает список
всех элементов, которые могут получить фокус внутри указанного
контейнера.ally.maintain.focus({ context }) — позволяет
“заморозить” фокус внутри контейнера, что особенно важно для модальных
окон и диалогов.ally.focus.trap({ context }) — более высокоуровневая
функция для реализации ловушек фокуса (focus trap), которая обеспечивает
циклическую навигацию между фокусируемыми элементами.Пример реализации ловушки фокуса для кастомного модального окна:
const modal = document.getElementById('customModal');
ally.focus.trap({
context: modal,
escapeDeactivates: true, // выход при нажатии Esc
clickOutsideDeactivates: true, // клик вне модального окна снимает фокус
});
Для кастомных переключателей или чекбоксов необходимо поддерживать правильные атрибуты ARIA:
role="switch" или role="checkbox" —
обозначает семантику элемента.aria-checked="true|false" — отображает текущее
состояние.tabindex="0" — делает элемент фокусируемым через
клавиатуру.Изменение состояния с обновлением ARIA:
const toggle = document.getElementById('customToggle');
toggle.addEventListener('click', () => {
const isChecked = toggle.getAttribute('aria-checked') === 'true';
toggle.setAttribute('aria-checked', !isChecked);
});
toggle.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
toggle.click();
}
});
ally.js облегчает управление клавиатурными событиями для кастомных контролов:
ally.query.tabster({ context }) — позволяет получать
последовательность табуляции элементов.document.addEventListener('keydown', (event) => {
if (event.target.classList.contains('custom-slider')) {
if (event.key === 'ArrowRight') {
moveSliderNext(event.target);
} else if (event.key === 'ArrowLeft') {
moveSliderPrev(event.target);
}
}
});
Для динамически создаваемых элементов ally.js предоставляет инструменты для отслеживания изменений и их доступности:
ally.query.focusable({ context, includeContext: true })
— позволяет проверять новые элементы после их добавления в DOM.ally.focus.visible({ context }) — помогает определить,
какой элемент имеет видимый фокус для корректного взаимодействия с
клавиатурой и экранными читалками.Пример для динамически добавленных кнопок:
const container = document.getElementById('dynamicControls');
function addButton(label) {
const button = document.createElement('button');
button.textContent = label;
container.appendChild(button);
const focusable = ally.query.focusable({ context: container });
console.log('Focusable elements:', focusable);
}
При интеграции кастомных контролов с формами важно:
<input> с
помощью aria-hidden="true" для элемента input, если нужно
сохранить стандартное поведение формы.const hiddenInput = document.querySelector('#realCheckbox');
toggle.addEventListener('click', () => {
const isChecked = toggle.getAttribute('aria-checked') === 'true';
hiddenInput.checked = isChecked;
});
ally.focus.trap и
ally.query.focusable для управления фокусом внутри формы,
если есть модальные окна или всплывающие панели.Для сложных интерфейсов, таких как вкладки или набор радиокнопок, нужно управлять фокусом внутри группы:
role="tablist" и role="tab" — для
вкладок.aria-selected="true|false" — текущая выбранная
вкладка.const tabs = document.querySelectorAll('[role="tab"]');
tabs.forEach(tab => {
tab.addEventListener('keydown', (event) => {
let index = Array.prototype.indexOf.call(tabs, event.target);
if (event.key === 'ArrowRight') {
tabs[(index + 1) % tabs.length].focus();
} else if (event.key === 'ArrowLeft') {
tabs[(index - 1 + tabs.length) % tabs.length].focus();
}
});
});
Использование ally.js позволяет при этом проверять доступность всех элементов группы и корректно управлять фокусом после динамических изменений.
tabindex или стандартные элементы).role, aria-checked,
aria-selected) обеспечивают совместимость с экранными
читалками.Эти принципы позволяют создавать полностью доступные кастомные элементы, которые интегрируются с существующими формами и компонентами интерфейса, обеспечивая удобство использования для всех пользователей.