- Вы не знаете как должны работать модальные окна
- Определение модального окна
- Теги и атрибуты
- Внешний вид и содержание
- Заголовок обязателен
- Статический контент должен быть связан с окном
- Интерактивные элементы связывать не нужно
- Способы закрыть окно
- Поведение фокуса
- При открытии диалога
- Внутри диалога
- При закрытии диалога
- Пример
- Подводя итог
- Дополнительные ссылки
Вы не знаете как должны работать модальные окна
Уверен, многие хоть раз создавали всплывающее модальное окно. Но задумывались ли вы об определении этого компонента? Как он должен работать?
В этом материале я постарался собрать максимально полный свод правил, рекомендаций и примеров реализации по которым модальные окна должны работать.
Я покажу, как просто создавать сложные, удобные, производительные и доступные модальные окна независимо от браузера, платформы, устройства или способа взаимодействия пользователя.
Этот список сформирован на основе спецификаций WAI-ARIA, HTML Living Standard и моего личного опыта. И хотя я буду говорить про веб, большинство правил и рекомендаций применимы для модальных окон где угодно.
Определение модального окна
Модальное окно — это окно наложенное либо на документ, либо на другие окна. При этом, любой контент под модальным окном является недоступным для взаимодействия.
Теги и атрибуты
Простейшая реализация кнопки открывающая диалог по его id:
Для различных диалогов, уведомлений и прочих перекрывающих документ элементов существует тег . Его вы и должны использовать. К огромному сожалению, его поддержка не самая лучшая:
- Chromium — полная поддержка.
- Firefox — поддержка за флагом.
- Safari не поддерживает вовсе.
Так что для этих браузеров нужно подгружать polyfill:
if (!document.createElement('dialog').showModal) < // Браузер нативно не поддерживает элемент dialog import('/dist/dialog-polyfill.js') // Подгружаем polyfill .then(dialogPolyfill =>document.querySelectorAll('dialog') .forEach(dialogPolyfill.registerDialog) // Применяем его для всех элементов на странице ) >
Вы, конечно, можете использовать и другой элемент для реализации диалогового окна, например так:
но тогда вам придётся самостоятельно реализовывать всё поведение описанное далее. В то время как с большую часть браузер реализует из коробки.
Внешний вид и содержание
Вскользь коснусь внешнего вида.
На небольших экранах диалоговое окно должно занимать 100% его размера. Если ваш диалог будет большим:
- Его будет легче «нащупать». Дело в том, что пользователь может взаимодействовать со страницей следующим образом: он водит пальцем по дисплею, а программа чтения с экрана озвучивает то, что в данный момент находится под пальцем.
- Пользователю гарантированно не будут озвучиваться элементы «под ним». Иначе, например, VoiceOver на iPad может озвучивать отдельные фрагменты страницы под модальным окном даже «сквозь» оверлей блокирующий доступ указателю.
- Вы скроете прокрутку фона на некоторых устройствах при прокрутке контента в диалоговом окне.
- Удобнее для одной руки. Если окно растянуто на всю высоту – то у вас есть возможность прижать кнопки управления к нижней части дисплея. Туда намного проще дотянуться одной рукой пользователям современных смартфонов.
- Больше места для контента на устройствах с маленьким экраном, таких как iPhone SE.
Заголовок обязателен
У модального окна, как у любой обычной страницы, должен быть свой заголовок. Короткий, точно описывающий его предназначение. Наличие заголовка намного упрощает восприятие пользователем.
Настоятельно рекомендуется использовать для заголовка тег — .
Но просто добавить заголовок в диалоговое окно недостаточно. Их нужно ещё и логически «связать». Сделать это можно с помощью атрибута aria-labelledby следующим образом:
Теперь, при попадании пользователя в диалоговое окно, в случае с экранным диктором, будет зачитан не только факт наличия диалога, но и его заголовок.
Статический контент должен быть связан с окном
Если в вашем диалоговом окне есть какое-то не интерактивное содержание, например, абзац текста, его стоит связать с диалогом подобно заголовку. Иначе, в некоторых случаях программы чтения с экрана не будут озвучивать такой контент.
Делается это атрибутом aria-describedby :
Если в вашем диалоговом окне много контента, тогда стоит обернуть его в один и связать элемент диалога уже с ним:
Важно! Заголовок и любые кнопки не относящиеся к содержимому, а служащие для управления диалоговым окном, не должны быть включены в элемент на который указывает aria-describedby . Они должны быть вынесены отдельно:
Интерактивные элементы связывать не нужно
Есть другой сценарий, когда содержимое вашего окна состоит из формы без предшествующего ей текста. В таком случае нет необходимости связывать форму с окном:
Элементы формы являются интерактивными. И они будут озвучены скринридером, когда пользователь начнёт с ними взаимодействовать.
Если скомбинировать и статический текст и форму:
Способы закрыть окно
Дополнительно, в зависимости от вашей логики, вы можете позволить пользователю закрыть диалог кликнув за его пределами или нажав Escape (встроено в из коробки).
- Не рассчитывайте, что пользовать всегда может нажать на оверлей и так закрыть диалог.
- Как я писал ранее, во многих случаях диалоговое окно может занимать всю или большую часть экрана. Таким образом попасть в него может быть сложно или невозможно.
- Такой оверлей семантически не считается интерактивным элементом. Он не может быть в фокусе и на него невозможно «нажать» клавишами.
Простейшая реализация кнопки закрывающей родительский диалог:
А если вы делаете кнопку с иконкой, то не забывайте про подпись, чтобы передать ёё назначение:
Поведение фокуса
При открытии диалога
Во время открытия диалогового окна фокус должен быть перемещён на элемент внутри него. На какой именно — зависит от содержания.
В общем случае фокус перемещается на первый интерактивный элемент. Именно так ведет себя нативный в браузере. Но нельзя делать сам элемент окна фокусируемым и перемещать фокус на него.
Например, для диалога с формой первый интерактивный элемент это первый . Если ваше диалоговое окно носит чисто информативный характер, например, уведомление об успешной подписке, тогда первым и единственным элементом будет кнопка закрывающая диалог.
Но есть и несколько исключений:
- Запрос подтверждения чего-либо. Если ваш диалог запрашивает у пользователя подтверждения перед выполнением каких-то необратимых действий (удаление чего-то или выполнение финансовых операций), тогда фокус автоматически должен ставится на кнопку «отмены» этих действий, независимо от её расположения.
- Ситуации, когда в диалоговом окне много статического контента и первый интерактивный элемент не помещается в видимую область. Проблема тут в том, что в таком случае браузер автоматически проскролит вниз к кнопке в фокусе. Это вынудит пользователя скролить обратно вверх, а потом снова вниз. Для таких случаев есть два подхода:
- Переместить или продублировать интерактивные элементы так, чтобы первый из них был в видимой части экрана. Например, выполнить кнопку закрыть в виде крестика и закрепить в верхней части диалогового окна.
- Заголовок или первый абзац текста нужно сделать фокусируемым при помощи tabindex=»-1″ и перемещать фокус на него. Но при этом подходе некоторые программы чтения с экрана могут озвучивать заданный текст дважды: сначала как заголовок и описание окна, а потом как содержание выделенного элемента.
Управлять куда именно попадёт фокус при открытии модального окна можно с помощью атрибута autofocus :
Внутри диалога
Особенность модального окна в том, что оно перекрывает собой весь документ не давая возможность с ним взаимодействовать.
Чтобы блокировать указатель обычно документ накрывается полупрозрачным блоком.
Но этого недостаточно, так как остаётся ещё и навигация клавишами Tab / Shift + Tab . Также это могут быть клавиши громкости на смартфонах или специальные клавиши на дополнительных инструментах подключенных по USB/Bluetooth. Этот способ навигации тоже должен быть заблокирован.
После попадания фокуса в модальное окно пользователь может перебирать интерактивные элементы внутри этого окна, но не должен выходить за его пределы. Другими словами, такое диалоговое окно работает как ловушка для фокуса. Это поведение встроено в , так что от вас никаких действий не требуется. А вот используя другой элемент с role=»dialog» его нужно реализовывать самостоятельно средствами JavaScript.
При закрытии диалога
При закрытии диалогового окна фокус должен быть перемещён туда, где он был в момент открытия. Это поведение не является частью и браузер полностью оставляет это на усмотрение разработчика.
Но и тут есть одно исключение: если элемент более не доступен, тогда фокус нужно вернуть туда, откуда наиболее логично для пользователя продолжить работу.
Пример
Предлагаю разобрать на примере. Представим систему из трех диалоговых окон:
- Сообщает пользователю об наличии подписки. В нем две кнопки: «Условия подписки» и «Подписаться»
- Отображается по клику на «Условия подписки». Открывается поверх первого.
- Отображается по клику на «Подписаться». Заменяет собой первое.
В примерах ниже я специально пропустил дополнительные атрибуты и элементы, для упрощения кода.
Итак, у нас есть стартовая кнопка.
По нажатию на неё открывается первый диалог. Фокус автоматически перемещается на первый интерактивный элемент. А закрытие диалога должно возвращать фокус назад.
┌► │ └─
Далее пользователь перемещает фокус на «Условия подписки» и нажимает. Открывается второй диалог поверх первого. Фокус перемещается в него, а возвращаться должен на эту же кнопку в первом диалоге:
┌► │ └─ │ └─
После закрытия второго диалога ваш JavaScript должен вернуть фокус на кнопку «Условия подписки» в первом.
┌► │ └─
После чего пользователь нажимает кнопку «Подписаться». По условиям нашей задачи открывается третий диалог. Фокус автоматически перемещается в него. А первый диалог закрывается:
┌► │ └─ │ │ │ └─
И вот проблема: третье окно должно вернуть фокус на кнопку в первом, но первое окно больше не доступно. В таких случаях фокус нужно вернуть туда, куда указывал закрытый диалог — на кнопку «Рассылка» с которой пользовать начал.
┌► │ │ │ │ │ └─
Безусловно, в вашем конкретном случае может быть более логичное поведение для возвращения фокуса. Например, у вас диалог создания новой записи в таблице. В таком случае, может быть логичнее возвращать фокус на только что созданную запиcь.
Помните, как во время установки программы в Windows можно просто нажимать Enter? Так вот это пример хорошей работы с фокусом: каждый раз, при переходе на новый экран в фокус ставится элемент, с которым вы скорее всего будете взаимодействовать — кнопка «Далее» или «Обзор».
Подводя итог
- Используйте семантические теги и .
- Делайте ваши окна достаточно большими.
- В модальных окнах должен быть заголовок.
- Заголовок и содержимое должны быть соответствующим образом связаны с элементом модального окна.
- Убедитесь что в диалоге есть кнопка для закрытия окна.
- Перемещайте фокус внутрь при открытии, не выпускайте его из модального окна и возвращайте туда, где он был после закрытия.
Дополнительные ссылки