<dialog> - тег для модального окна: сам по себе он ничего не блокирует, но метод showModal() из JavaScript даёt настоящую модальность - подложку, блокировку остальной страницы и закрытие по Esc, без единой JS-библиотеки. А form method="dialog" умеет закрывать окно и передавать результат клика без единой строчки лишнего кода.
Заметь: нажми "Удалить файл" - откроется настоящее модальное окно (фон затемнён, страница за ним неактивна). Нажми "Отмена" или "Удалить" - окно закроется само, а текст под кнопкой обновится значением returnValue, которое взялось из атрибута value нажатой кнопки. Ни одной строчки кода для управления фокусом или блокировкой страницы писать не пришлось.
Открыть модальное окно: showModal()
<dialog> - обычный контейнер, если его просто добавить в разметку. Настоящую модальность даёт только вызов метода showModal() из JavaScript: страница за окном становится по-настоящему неактивной (клик мимо окна ничего не делает), появляется затемнённая подложка (её можно оформить через псевдоэлемент ::backdrop), а нажатие Esc закрывает окно. Если вместо showModal() просто написать атрибут open в разметке (или вызвать более слабый метод show()), получится немодальное окно: страница вокруг остаётся кликабельной, Esc его не закрывает, а сам браузер не воспринимает это как :modal. Разница не в том, как окно выглядит, а именно в поведении - оба варианта окно нарисуют одинаково, но только showModal() реально блокирует остальную страницу. Дописывать тегу dialog атрибут tabindex не нужно - сам по себе он не интерактивен, фокус при showModal() автоматически уходит на первый доступный элемент внутри (или на элемент с autofocus).
<script>document.getElementById("d").showModal()</script> - настоящее модальное окно: страница за ним заблокирована.
<dialog open><p>Текст</p></dialog> - без showModal() это немодальное окно: страница вокруг по-прежнему кликабельна, Esc не действует.
Закрыть правильно: method=dialog и returnValue
Форма с атрибутом method="dialog" внутри dialog закрывает окно при отправке без обращения к серверу и без перезагрузки страницы, а свойство dialog.returnValue получает значение атрибута value нажатой кнопки - это можно прочитать в обработчике события close. Нажатие Esc тоже закрывает окно, но returnValue при этом не обновляется. Отменить закрытие по Esc можно только через отдельное событие cancel, которое срабатывает непосредственно перед закрытием - вызов preventDefault() внутри его обработчика оставит окно открытым. Частое заблуждение - что клик по затемнённой подложке снаружи окна закроет его сам по себе: по умолчанию это не так, для такого поведения нужен либо свой код, либо новый атрибут closedby="any", который на момент написания урока ещё не поддерживается частью основных браузеров. Поэтому в диалоге всегда должна быть явная видимая кнопка закрытия - иначе пользователю без физической клавиши Esc (например, на сенсорном экране) будет попросту нечем закрыть окно.
<form method="dialog"><button value="ok">ОК</button></form> - закрывает окно, returnValue станет "ok".
<dialog><p>Все данные будут удалены</p></dialog> - нет ни одной видимой кнопки закрытия, на сенсорном экране без Esc окно нечем закрыть.
Частые ошибки новичков
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| Открывать dialog только атрибутом open в разметке | Получается немодальное окно: страница вокруг кликабельна, Esc не закрывает | Вызывать showModal() из JavaScript для настоящей модальности |
| Не давать явную видимую кнопку закрытия | На сенсорных устройствах без физической Esc окно нечем закрыть | Всегда добавлять видимую кнопку "Закрыть" или "Отмена" |
| Ожидать, что клик по затемнённой подложке закроет окно сам по себе | По умолчанию клик снаружи окна ничего не делает | Закрывать явно - form method=dialog, close() или (в перспективе) атрибут closedby |
| Добавлять tabindex самому тегу dialog | Тег не интерактивен, атрибут лишний и может сломать порядок фокуса | Фокус ставить на кнопки или поля внутри dialog, не на сам тег |
| Пытаться остановить закрытие по Esc произвольным кодом на keydown | Встроенное закрытие по Esc отменяется только через событие cancel | Слушать cancel на самом dialog и вызывать preventDefault() в нём |
Шпаргалка
| Тег/метод | Что нужно знать |
|---|---|
| <dialog> | Контейнер окна; для настоящей модальности нужен JS-вызов showModal() |
| showModal() | Модальное окно: подложка ::backdrop, блокировка страницы, закрытие по Esc |
| show() / голый open | Немодальное окно: страница активна, Esc не закрывает |
| form method="dialog" | Закрывает окно без отправки на сервер, returnValue = value нажатой кнопки |
| cancel + preventDefault() | Единственный способ отменить закрытие по Esc |
Коротко: отвечаем на вопросы
Чем showModal() отличается от простого атрибута open?
showModal() даёт настоящую модальность: остальная страница блокируется от кликов, появляется затемнённая подложка, а Esc закрывает окно. Голый атрибут open (или метод show()) даёт немодальное окно - страница вокруг остаётся полностью активной, а Esc на него не действует.
Закроется ли окно, если кликнуть по затемнённой подложке снаружи?
Нет, по умолчанию клик по подложке никак не обрабатывается и окно остаётся открытым. Для такого поведения нужен либо свой код обработки клика, либо новый атрибут closedby, который пока поддерживается не всеми основными браузерами.
Что делает form method="dialog" внутри окна?
Закрывает dialog без отправки данных на сервер и без перезагрузки страницы. Значение атрибута value нажатой кнопки записывается в dialog.returnValue, которое можно прочитать в обработчике события close.
Можно ли отменить закрытие окна по нажатию Esc?
Да, но не обработкой keydown, а через отдельное событие cancel, которое срабатывает непосредственно перед закрытием. Вызов preventDefault() в его обработчике оставит окно открытым.