Создание accessible модальных окон

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


Управление фокусом внутри модального окна

Для модального окна критически важно, чтобы фокус оставался внутри него до момента закрытия. ally.js предоставляет метод trapFocus, который реализует замыкание фокуса:

import { trapFocus, removeTrapFocus } from 'ally.js';

const modal = document.querySelector('#myModal');

// Активация замыкания фокуса
trapFocus({
  context: modal,
  initialFocus: modal.querySelector('[autofocus]') || modal,
  escapeDeactivates: true
});

// Деактивация при закрытии
removeTrapFocus(modal);

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

  • context — DOM-элемент, внутри которого необходимо удерживать фокус.
  • initialFocus — элемент, на котором должен оказаться фокус при открытии модального окна.
  • escapeDeactivates — позволяет закрывать модальное окно с клавишей Esc.

Скрытие фонового контента

Чтобы пользователь не мог взаимодействовать с элементами страницы за модальным окном, нужно временно скрывать их из доступности. ally.js поддерживает атрибут aria-hidden, что позволяет программно скрыть все остальные элементы страницы:

import { hideOthers } from 'ally.js';

const modal = document.querySelector('#myModal');
const restore = hideOthers(modal);

// restore() снимает скрытие при закрытии модального окна

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

  • hideOthers автоматически добавляет aria-hidden="true" ко всем элементам вне модального окна.
  • Возвращаемая функция restore необходима для восстановления исходного состояния DOM после закрытия окна.

Обработка клавиатурных событий

Для улучшения доступности важно корректно обрабатывать клавиши навигации:

modal.addEventListener('keydown', (event) => {
  if (event.key === 'Escape') {
    closeModal();
  }
});

ally.js в сочетании с trapFocus обеспечивает циклическое перемещение фокуса при нажатии клавиш Tab и Shift+Tab, предотвращая выход фокуса за пределы модального окна.


Адаптация для динамического контента

Модальные окна часто создаются динамически. ally.js поддерживает динамическое обновление фокусируемых элементов:

trapFocus({
  context: modal,
  updateOnMutation: true
});
  • updateOnMutation отслеживает изменения DOM внутри модального окна.
  • Позволяет корректно работать с контентом, который подгружается асинхронно или меняется после открытия.

Настройка initialFocus и fallbackFocus

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

trapFocus({
  context: modal,
  initialFocus: modal.querySelector('.first-input'),
  fallbackFocus: modal.querySelector('button.close')
});
  • initialFocus — первичный элемент для фокуса.
  • fallbackFocus — используется, если initialFocus недоступен, что предотвращает потерю фокуса.

Интеграция с анимацией открытия и закрытия

Модальные окна часто имеют анимацию. ally.js учитывает задержку фокуса, позволяя синхронизировать его с CSS-анимациями:

setTimeout(() => {
  trapFocus({ context: modal });
}, 300); // 300ms соответствует длительности анимации

Полный пример модального окна

import { trapFocus, removeTrapFocus, hideOthers } from 'ally.js';

const modal = document.querySelector('#myModal');
const openButton = document.querySelector('#openModal');
const closeButton = modal.querySelector('.close');

let restoreHidden;

openButton.addEventListener('click', () => {
  modal.style.display = 'block';
  restoreHidden = hideOthers(modal);

  trapFocus({
    context: modal,
    initialFocus: modal.querySelector('input'),
    escapeDeactivates: true
  });
});

closeButton.addEventListener('click', () => {
  modal.style.display = 'none';
  restoreHidden();
  removeTrapFocus(modal);
});

Особенности реализации:

  • Модальное окно становится видимым и фокусируется на первом интерактивном элементе.
  • Остальной контент страницы скрывается с помощью aria-hidden.
  • При закрытии окно скрывается, фокус снимается, и скрытые элементы возвращаются в доступность.

Лучшие практики использования ally.js для модальных окон

  1. Всегда задавать initialFocus или autofocus, чтобы пользователь сразу попадал на логичный элемент.
  2. Использовать hideOthers для предотвращения взаимодействия с остальной страницей.
  3. Обеспечивать корректное закрытие через Esc, кнопки и программные вызовы.
  4. При динамическом контенте включать updateOnMutation для корректного обновления фокусируемых элементов.
  5. В случае анимации синхронизировать активацию trapFocus с завершением анимации.

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