
Чтобы управлять фокусом корректно, при открытии модального окна переведите его на подходящий элемент внутри диалога, а при закрытии верните на элемент, который запустил открытие. Для нативного элемента <dialog> используйте showModal(): браузер обеспечивает модальность, а JavaScript сохраняет и восстанавливает фокус с проверкой доступности исходной кнопки.
Ориентиры для безопасного управления фокусом
- Выбирайте начальную точку фокуса с учётом содержимого диалога: это может быть первое поле или заголовок.
- При открытии модального диалога не оставляйте клавиатурный фокус за его пределами.
- Запоминайте элемент, вызвавший открытие, до показа диалога.
- Перед восстановлением фокуса проверяйте, что исходный элемент всё ещё находится в документе и доступен.
- Проверяйте сценарии с клавиатурой и скринридером, а не только мышью.
Как определить элемент для фокуса при открытии модального окна
Сначала решите, какую задачу пользователь должен выполнить в диалоге. Для формы логично направить фокус на первое поле ввода; для длинного текста или предупреждения - на заголовок либо краткое описание с tabindex="-1". Это полезно, когда человек должен сразу понять контекст, не проходя клавишей Tab через весь текст.
Не перемещайте фокус без необходимости, если окно не является действительно модальным или не перекрывает текущий сценарий. Ошибочная смена фокуса может сбить пользователя, который читает страницу или работает с вспомогательными технологиями. При проектировании того, как создать модальное окно на сайте, сначала определите его роль и поведение при открытии, закрытии и отмене.
Перевод фокуса внутрь диалога без потери доступности
Для базовой реализации понадобятся:
- семантическая разметка: предпочтительно элемент
<dialog>для модального сценария; - понятное доступное имя через
aria-labelledbyили, если заголовка нет, черезaria-label; - JavaScript, который запоминает инициатор, открывает диалог и обрабатывает закрытие;
- кнопка закрытия, доступная с клавиатуры, и видимый индикатор фокуса.
У нативного модального <dialog>, открытого методом showModal(), браузер ограничивает переходы фокуса содержимым диалога. Поэтому обычно не требуется вручную перехватывать каждый Tab. При использовании собственного контейнера вместо нативного элемента понадобится отдельно реализовать удержание фокуса, закрытие по Escape и управление остальной страницей.
Удержание фокуса в модальном окне и обработка Tab
Перед реализацией учтите риски и ограничения:
- Не смешивайте нативный
<dialog>с самодельным перехватом Tab без необходимости: обработчики могут конфликтовать. - Не ставьте фокус на неинтерактивный контейнер без
tabindex="-1". - Не скрывайте диалог визуально, оставляя его доступным для клавиатуры и скринридера.
- Проверьте поведение при закрытии через кнопку, Escape и программное действие.
-
Свяжите диалог с заголовком.
Добавьте заголовку идентификатор и укажите его вaria-labelledby. Это помогает скринридеру сообщить назначение окна. -
Сохраните инициатор и откройте окно.
Сохраните элемент, на котором находился фокус, до вызоваshowModal(). Для первого перевода фокуса задайте элементу внутри диалога атрибутautofocusлибо явно вызовитеfocus(). -
Обеспечьте предсказуемое закрытие.
Закрывайте нативный диалог методомclose(); событиеcloseиспользуйте для восстановления фокуса. Нативный диалог также поддерживает закрытие по Escape, если приложение не отключило это поведение. -
Проверьте переходы клавишей Tab.
При использованииshowModal()проверьте, что Tab и Shift+Tab перемещают фокус внутри окна. Для собственного диалога настройте цикл переходов между доступными элементами и обработку случая, когда таких элементов нет.
Пример минимальной связки с нативным диалогом:
const dialog = document.querySelector("#edit-dialog");
const title = document.querySelector("#edit-title");
let opener = null;
document.querySelector("#open-dialog").addEventListener("click", (event) => {
opener = event.currentTarget;
dialog.showModal();
title.focus();
});
dialog.addEventListener("close", () => {
if (opener?.isConnected && !opener.disabled) {
opener.focus();
}
});
В этом примере заголовку нужен атрибут tabindex="-1", а элементу <dialog> - связь с заголовком, например aria-labelledby="edit-title". Если в проекте возможно несколько способов закрытия, обработчик события close позволяет не дублировать восстановление фокуса.
Как вернуть фокус после закрытия диалога
Возвращайте фокус на инициатор открытия, если он по-прежнему существует и доступен. Если интерфейс изменился, выберите ближайший логичный элемент в текущем сценарии, например заголовок обновлённого раздела или кнопку следующего действия. Проверьте результат по списку:
- После открытия фокус оказался внутри диалога.
- Название и назначение окна понятны при чтении скринридером.
- Tab и Shift+Tab не уводят фокус за пределы модального окна.
- Кнопка закрытия доступна с клавиатуры и имеет понятное название.
- Закрытие по Escape работает, если это предусмотрено сценарием.
- После закрытия фокус возвращается на элемент, вызвавший открытие.
- Если инициатор исчез или стал недоступен, фокус переходит на логичную замену.
- Индикатор фокуса виден и до открытия, и после закрытия диалога.
Что делать, если исходный элемент исчез или стал недоступен
Такое бывает после отправки формы, обновления списка или смены маршрута. Не вызывайте focus() без проверки: фокус может не перейти на отсоединённый от документа или отключённый элемент. Рассмотрите запасную точку - например, контейнер обновлённого содержимого с tabindex="-1".
- Не сохранять инициатор до открытия диалога и затем восстанавливать фокус на другом элементе.
- Не переводить фокус на декоративный элемент, который не может его принять.
- Не добавлять
tabindex="0"всем элементам подряд: это меняет порядок клавиатурной навигации. - Не отключать Escape без обоснования и альтернативного способа закрытия.
- Не полагаться только на визуальное скрытие окна: закрытый диалог не должен оставаться доступным для взаимодействия.
- Не блокировать прокрутку или фоновые элементы так, чтобы пользователь не мог завершить сценарий.
Проверка сценариев фокуса с клавиатурой и скринридером
- Нативный диалог: выбирайте его для стандартного модального интерфейса. Проверьте
showModal(), переходы Tab, Escape и возврат фокуса в целевых браузерах. - Собственный диалог: используйте, если проект требует поведения, которого нет у нативного элемента. Реализуйте и тестируйте ограничение фокуса, доступное имя, блокировку фонового взаимодействия и закрытие.
- Автоматизированные проверки: используйте их для выявления типичных ошибок разметки, но дополняйте ручной проверкой клавиатурой и скринридером.
- Ручной проход: откройте диалог клавиатурой, пройдите элементы вперёд и назад, закройте его разными способами и убедитесь, что фокус продолжает сценарий без неожиданного скачка.
Качественное управление фокусом в модальном окне означает, что пользователь понимает, где находится, может выполнить действие и после закрытия продолжает работу с ожидаемого места. Доступное модальное окно HTML ARIA требует корректной семантики, а JavaScript должен поддерживать эту семантику, а не заменять её.
Практические ситуации при работе с фокусом в диалогах
Нужно ли всегда ставить фокус на первую кнопку?
Нет. Для формы уместно первое поле, для длинного пояснения - заголовок или вводный текст. Выбирайте начальную точку по задаче пользователя.
Нужно ли вручную перехватывать Tab у элемента dialog?
Обычно нет, если модальное окно открыто нативным методом showModal(). Для собственного контейнера удержание фокуса необходимо реализовать и протестировать отдельно.
Как вернуть фокус после закрытия модального окна?
Сохраните элемент-инициатор перед открытием и верните фокус на него после закрытия, если он ещё в документе и доступен. Иначе задайте логичную запасную точку.
Что делать, если кнопка открытия удалена после отправки формы?
Не пытайтесь фокусировать удалённую кнопку. Переведите фокус на элемент, который отражает результат операции, например на заголовок обновлённого блока.
Можно ли использовать autofocus внутри диалога?
Да, но только после проверки сценария и поддержки в целевых браузерах. Для более явного управления можно вызвать focus() после открытия.
Как проверить диалог без скринридера?
Пройдите сценарий клавишами Tab, Shift+Tab, Enter, пробел и Escape. Затем дополните проверку скринридером, чтобы оценить объявление названия и состояния диалога.
Нужно ли закрывать модальное окно по клику на затемнённый фон?
Это решение интерфейса, а не обязательное требование управления фокусом. Если фон закрывает окно, убедитесь, что такое поведение предсказуемо и не приводит к случайной потере введённых данных.
Автор: Ирина Белова.