Back

Распространенные проблемы доступности модальных окон (и способы их решения)

Распространенные проблемы доступности модальных окон (и способы их решения)

Модальные диалоги повсеместно используются в современных веб-приложениях, но они также являются одним из самых распространенных источников проблем с доступностью. Модальное окно — это диалоговое окно, которое появляется поверх основного содержимого и требует взаимодействия с пользователем перед продолжением работы. При неправильной реализации модальные окна могут заблокировать пользователей клавиатуры, запутать программы чтения с экрана и создать фрустрирующий опыт для всех, кто полагается на вспомогательные технологии. Рассмотрим наиболее частые проблемы доступности модальных окон и практические решения для них.

Ключевые выводы

  • Управление фокусом критически важно: перемещайте фокус на модальное окно при открытии и возвращайте его к элементу-триггеру при закрытии
  • Используйте правильные ARIA-атрибуты, включая role=“dialog”, aria-modal=“true” и доступные метки
  • Реализуйте полную клавиатурную навигацию с циклическим переходом по Tab и поддержкой клавиши Escape
  • Сделайте фоновое содержимое полностью неактивным, пока модальное окно открыто
  • Тестируйте с реальными вспомогательными технологиями, а не только автоматизированными инструментами

Критическая роль управления фокусом

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

Распространенная ошибка: Фокус остается на фоновом содержимом, позволяя пользователям переходить по элементам за модальным окном.

Решение: Реализуйте правильное управление фокусом:

// При открытии модального окна
const triggerElement = document.activeElement;
modal.showModal(); // или modal.focus() для пользовательских реализаций

// При закрытии модального окна
modal.close();
triggerElement.focus();

Для React-приложений с использованием focus-trap-react:

import FocusTrap from 'focus-trap-react';

function Modal({ isOpen, onClose, children }) {
  return (
    <FocusTrap active={isOpen}>
      <div role="dialog" aria-modal="true">
        {children}
      </div>
    </FocusTrap>
  );
}

Отсутствующие или неправильные ARIA-атрибуты

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

Распространенные ошибки:

  • Отсутствие role="dialog" или role="alertdialog"
  • Отсутствие aria-modal="true"
  • Отсутствие доступного имени через aria-label или aria-labelledby

Решение: Используйте подходящие ARIA-атрибуты:

<!-- Использование нативного элемента dialog (рекомендуется) -->
<dialog aria-labelledby="modal-title" aria-describedby="modal-desc">
  <h2 id="modal-title">Подтверждение удаления</h2>
  <p id="modal-desc">Это действие нельзя отменить.</p>
</dialog>

<!-- Использование div с ARIA -->
<div role="dialog" 
     aria-modal="true" 
     aria-labelledby="modal-title"
     aria-describedby="modal-desc">
  <!-- Содержимое модального окна -->
</div>

Используйте role="alertdialog" для критических предупреждений, требующих немедленного ответа пользователя, поскольку это вызывает более настойчивые объявления программы чтения с экрана.

Нарушенная клавиатурная навигация

Каждое модальное окно должно быть полностью доступно с клавиатуры. Пользователи должны перемещаться по интерактивным элементам с помощью Tab/Shift+Tab и закрывать модальное окно клавишей Escape.

Распространенные ошибки:

  • Отсутствие поддержки клавиши Escape
  • Фокус может покинуть модальное окно (отсутствие ловушки фокуса)
  • Порядок табуляции не соответствует визуальному расположению

Решение: Реализуйте полную ловушку фокуса:

function trapFocus(modalElement) {
  const focusableElements = modalElement.querySelectorAll(
    'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
  );
  
  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];
  
  modalElement.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') {
      closeModal();
      return;
    }
    
    if (e.key === 'Tab') {
      if (e.shiftKey && document.activeElement === firstElement) {
        e.preventDefault();
        lastElement.focus();
      } else if (!e.shiftKey && document.activeElement === lastElement) {
        e.preventDefault();
        firstElement.focus();
      }
    }
  });
  
  firstElement.focus();
}

Фоновое содержимое остается интерактивным

Когда модальное окно открыто, фоновое содержимое должно быть полностью неактивным. Пользователи не должны иметь возможность взаимодействовать с чем-либо за модальным окном.

Распространенная ошибка: Фон остается прокручиваемым или интерактивным через клавиатурную навигацию.

Решение: Сделайте фоновое содержимое неактивным:

// При открытии модального окна
document.body.style.overflow = 'hidden';
document.querySelector('main').setAttribute('aria-hidden', 'true');
document.querySelector('main').setAttribute('inert', ''); // Современный подход

// При закрытии модального окна
document.body.style.overflow = '';
document.querySelector('main').removeAttribute('aria-hidden');
document.querySelector('main').removeAttribute('inert');

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

Автоматизированные инструменты выявляют некоторые проблемы, но ручное тестирование остается необходимым:

  1. Тестирование клавиатуры:

    • Откройте модальное окно и убедитесь, что фокус перемещается на него
    • Пройдите по всем интерактивным элементам с помощью Tab
    • Убедитесь, что Tab циклически переходит внутри модального окна
    • Нажмите Escape для закрытия
    • Убедитесь, что фокус возвращается к элементу-триггеру
  2. Тестирование программы чтения с экрана:

    • Протестируйте с NVDA (Windows) или VoiceOver (macOS)
    • Убедитесь, что роль модального окна озвучивается
    • Проверьте, что заголовок и описание читаются
    • Убедитесь, что фоновое содержимое недоступно
  3. Визуальное тестирование:

    • Убедитесь, что индикаторы фокуса видны
    • Проверьте контрастность цветов (WCAG AA требует 4.5:1 для обычного текста, 3:1 для крупного текста и компонентов UI)
    • Убедитесь, что кнопки закрытия четко подписаны

Лучшие практики реализации

Используйте нативный элемент <dialog>, когда это возможно. Он обеспечивает встроенное управление фокусом и семантику ARIA:

const dialog = document.querySelector('dialog');
dialog.showModal(); // Открывается с правильной ловушкой фокуса
dialog.close();     // Закрывается и возвращает фокус

Для пользовательских реализаций следуйте этому чек-листу:

  • Установите role="dialog" и aria-modal="true"
  • Предоставьте доступное имя через aria-labelledby или aria-label
  • Реализуйте полную поддержку клавиатуры (Tab, Shift+Tab, Escape)
  • Создайте надежную ловушку фокуса
  • Отключите прокрутку и взаимодействие с фоном
  • Верните фокус к элементу-триггеру при закрытии
  • Включите видимые индикаторы фокуса
  • Тестируйте с реальными вспомогательными технологиями

Заключение

Доступные модальные окна — это не просто вопрос соответствия стандартам, они создают лучший опыт для всех пользователей. Решая эти распространенные проблемы, вы обеспечиваете бесшовную работу ваших модальных окон для пользователей клавиатуры, пользователей программ чтения с экрана и всех остальных. Начните с семантического HTML, добавьте правильные ARIA-атрибуты, реализуйте тщательное управление фокусом и всегда тестируйте с реальными вспомогательными технологиями.

Помните: если ваше модальное окно не работает без мыши, оно не работает. Исправьте эти проблемы, и ваши модальные окна станут действительно доступными для всех.

Часто задаваемые вопросы

Используйте role='dialog' для стандартных модальных окон, содержащих формы или информацию. Используйте role='alertdialog' для критических предупреждений, требующих немедленного ответа, поскольку это заставляет программы чтения с экрана озвучивать содержимое более настойчиво и прерывать текущую задачу пользователя.

CSS может визуально скрыть содержимое с помощью оверлеев и предотвратить события указателя, но он не остановит клавиатурную навигацию. Вам нужен JavaScript для добавления aria-hidden или атрибута inert, чтобы действительно сделать фоновое содержимое неинтерактивным для всех пользователей.

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

Truly understand users experience

See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..

OpenReplay