Олег Марков
Диалоговое окно в HTML dialog
Введение
HTML-элемент <dialog> — это встроенный способ создавать диалоговые и модальные окна без сторонних библиотек и сложной верстки. Он появился как часть спецификации HTML Living Standard и постепенно получил поддержку в современных браузерах.
Смотрите, здесь важно понимать одну вещь: <dialog> — это не просто «еще один див». Это семантический элемент, который:
- сообщает браузеру и вспомогательным технологиям (скринридерам), что перед нами диалоговое окно;
- умеет автоматически блокировать фон (режим модального окна);
- предоставляет удобные методы JavaScript для открытия и закрытия;
- имеет специальное поведение по фокусу и клавиатурному управлению.
Давайте разберемся, как этот элемент работает, какие у него есть особенности, и как вы можете использовать его в реальных интерфейсах.
Что такое <dialog> и когда его использовать
Краткое определение
Элемент <dialog> — это контейнер для контента диалогового окна. Внутрь него вы помещаете любую разметку: заголовки, текст, формы, кнопки.
Пример самой простой структуры:
<dialog id="exampleDialog">
<!-- Заголовок диалога -->
<h2>Подтверждение действия</h2>
<!-- Основной текст -->
<p>Вы уверены, что хотите выполнить это действие</p>
<!-- Кнопки управления -->
<button id="confirmBtn">Да</button>
<button id="cancelBtn">Отмена</button>
</dialog>Пока вы не откроете это окно из JavaScript или не добавите к нему атрибут open, оно не будет видно пользователю.
Когда имеет смысл использовать <dialog>
Семантический диалог удобен в таких сценариях:
- подтверждение действий (удаление, сброс, критические операции);
- формы, которые открываются поверх страницы (например, авторизация);
- всплывающие подсказки и уведомления с кнопками;
- «мастера» (step-by-step) в виде серии диалогов.
При этом <dialog> не обязан быть модальным. Его можно показывать как обычный всплывающий блок, который не блокирует остальной интерфейс. Для этого используются разные способы открытия, о них мы поговорим далее.
Базовое использование: разметка и атрибуты
Атрибут open
Ключевой атрибут — open. Он определяет, виден ли диалог:
<dialog open>
<!-- Этот диалог уже открыт при загрузке страницы -->
<p>Диалог открыт сразу</p>
</dialog>Комментарии к поведению:
- если атрибут
openприсутствует — диалог отображается; - если атрибут убрать — диалог исчезнет с экрана;
- браузер сам добавляет/убирает
open, когда вы вызываете методыshow()иshowModal().
Рекомендуется не ставить open прямо в HTML, а управлять отображением через JavaScript. Так у вас будет больше контроля над моментом появления окна.
Атрибут id
Как и у других элементов, атрибут id нужен, чтобы находить диалог в коде:
<dialog id="loginDialog">
<!-- Содержимое формы входа -->
</dialog>Дальше вы сможете получить к нему доступ:
// Ищем диалог в DOM
const loginDialog = document.getElementById('loginDialog')Атрибут formmethod, formaction и другие (через <form method="dialog">)
Сам <dialog> не отправляет формы, но он тесно связан с ними. Позже мы рассмотрим особый режим method="dialog" у формы, которая лежит внутри диалога. Это удобный способ закрывать окно с результатом (какая кнопка была нажата).
Открытие и закрытие диалога: методы и события
Чтобы вам было проще, давайте сразу посмотрим на полный пример, а потом разберем его по шагам.
<button id="openBtn">Открыть диалог</button>
<dialog id="exampleDialog">
<p>Простейший диалог</p>
<button id="closeBtn">Закрыть</button>
</dialog>
<script>
// Получаем ссылки на элементы
const dialog = document.getElementById('exampleDialog')
const openBtn = document.getElementById('openBtn')
const closeBtn = document.getElementById('closeBtn')
// Открываем диалог как модальное окно
openBtn.addEventListener('click', () => {
dialog.showModal() // Блокирует фон
})
// Закрываем диалог
closeBtn.addEventListener('click', () => {
dialog.close() // Скрывает и вызывает событие close
})
</script>Посмотрим, что здесь важно.
Метод show()
Метод show() открывает диалог без модального режима:
// Открываем диалог, НЕ блокируя фон
dialog.show()Особенности:
- диалог становится видимым;
- контент под ним остается доступен;
- пользователь может взаимодействовать с остальной страницей;
- фокус не обязательно будет внутри диалога (нет жесткой «ловушки» фокуса).
show() полезен для некритичных всплывающих блоков: подсказок, небольших уведомлений.
Метод showModal()
Метод showModal() включает модальный режим:
// Открываем диалог в модальном режиме
dialog.showModal()При этом:
- диалог визуально «выходит на передний план»;
- элементы страницы под ним становятся недоступны для фокуса;
- Tab-клавиатура «ходит» только по элементам внутри диалога;
- браузер может добавить фон, который нельзя кликать (зависит от стилей).
Это поведение и делает <dialog> полноценным модальным окном «из коробки».
Метод close()
Метод close() закрывает диалог:
// Закрываем диалог, передаем значение результата
dialog.close('confirmed') // это значение попадет в dialog.returnValueОсобенности:
- диалог исчезает с экрана;
- из DOM он не удаляется, так что можно снова открыть;
- генерируется событие
close, на которое можно подписаться; - можно передать строку — результат диалога (например, какая кнопка нажата).
События cancel и close
Событие cancel
Событие cancel срабатывает, когда пользователь пытается закрыть модальный диалог «снаружи». Например, нажимает Esc.
Пример обработки:
dialog.addEventListener('cancel', (event) => {
// Здесь вы можете отменить закрытие
event.preventDefault() // Диалог не закроется
// Можно показать предупреждение или выполнить иной код
})Обратите внимание:
cancelсрабатывает только дляshowModal();- если вы не отмените событие через
event.preventDefault(), диалог закроется автоматически.
Событие close
Событие close возникает каждый раз, когда диалог закрывается методом close() или через браузерное поведение:
dialog.addEventListener('close', () => {
// Смотрим, какое значение было передано при закрытии
console.log('Диалог закрыт со значением', dialog.returnValue)
})Событие close удобно использовать:
- чтобы обработать результат;
- чтобы сбросить состояние формы;
- чтобы вернуть фокус обратно к кнопке, которая открывала диалог.
Работа с фокусом и доступностью (a11y)
Диалоговые окна тесно связаны с управлением фокусом. Если фокус «теряется» или перескакивает в неожиданные места, интерфейс становится неудобным, особенно для пользователей, которые работают с клавиатурой или скринридерами.
Что делает браузер автоматически
Когда вы вызываете showModal():
- диалог получает фокус или один из его фокусируемых элементов;
- нажатие Tab и Shift+Tab «ходит» только внутри диалога;
- фон становится недоступен для фокуса.
Но даже с этим, вы как разработчик должны:
- вернуть фокус туда, откуда диалог был открыт;
- обеспечить понятные тексты, заголовки, подписи к кнопкам;
- не блокировать Esc без необходимости.
Возвращаем фокус после закрытия
Смотрите, я покажу вам, как это реализовать на практике:
const dialog = document.getElementById('exampleDialog')
const openBtn = document.getElementById('openBtn')
// Храним элемент, который открыл диалог
let lastActiveElement = null
openBtn.addEventListener('click', () => {
// Запоминаем, кто был в фокусе
lastActiveElement = document.activeElement
dialog.showModal()
})
// После закрытия возвращаем фокус
dialog.addEventListener('close', () => {
if (lastActiveElement) {
lastActiveElement.focus()
}
})Так вы обеспечите плавное взаимодействие: пользователь нажимает кнопку, видит диалог, закрывает его — и снова попадает на ту же кнопку.
ARIA-атрибуты и заголовки
Семантика <dialog> уже говорит скринридеру, что это диалог, но важно дать понятное название:
<dialog id="profileDialog" aria-labelledby="profileDialogTitle">
<h2 id="profileDialogTitle">Редактирование профиля</h2>
<form method="dialog">
<!-- Поля формы -->
<button value="save">Сохранить</button>
<button value="cancel">Отмена</button>
</form>
</dialog>Комментарии:
aria-labelledbyуказывает, какой элемент описывает диалог;- заголовок
h2сidстановится «именем» окна для скринридера.
Диалог и формы: method="dialog" и returnValue
Как работает method="dialog"
Внутри диалога вы можете разместить форму с особым атрибутом method="dialog":
<dialog id="feedbackDialog">
<form method="dialog">
<!-- Текст сообщения -->
<p>Оцените работу сайта</p>
<!-- Элементы управления -->
<button value="like">Нравится</button>
<button value="dislike">Не нравится</button>
</form>
</dialog>Обратите внимание на две вещи:
method="dialog"говорит браузеру: при нажатии кнопки отправки формы — закрыть диалог.- Атрибут
valueна кнопке становится значениемdialog.returnValue.
Теперь давайте посмотрим, как это выглядит в коде:
const feedbackDialog = document.getElementById('feedbackDialog')
// Открываем диалог
document.getElementById('openFeedback').addEventListener('click', () => {
feedbackDialog.showModal()
})
// Слушаем закрытие
feedbackDialog.addEventListener('close', () => {
// Здесь мы читаем результат
console.log('Результат диалога', feedbackDialog.returnValue)
// Например, можно отправить аналитику
})То есть:
- нажатие кнопки «Нравится» автоматически закрывает диалог и устанавливает
returnValue = 'like'; - нажатие «Не нравится» —
returnValue = 'dislike'.
Вам не нужно вручную вызывать close() из JavaScript — за вас это делает браузер.
Комбинирование с обычной отправкой формы
Если вы хотите, чтобы форма внутри диалога отправлялась на сервер, вы можете использовать обычный method="post" и action. Но при этом стоит продумать, как вы будете закрывать диалог после успешной отправки (обычно это делается уже на новой странице или через AJAX).
Стилизация диалога
Элемент <dialog> можно стилизовать с помощью CSS, как и любой другой тег. Однако у него есть несколько особенностей.
Базовая стилизация
Давайте разберемся на примере:
<dialog id="styledDialog">
<h2>Настроенный диалог</h2>
<p>Здесь мы изменили внешний вид окна</p>
<button>Ок</button>
</dialog>/* Стили самого диалога */
#styledDialog {
border: none; /* Убираем стандартную рамку */
border-radius: 8px; /* Скругляем углы */
padding: 20px; /* Внутренний отступ */
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); /* Тень для объема */
width: 400px; /* Фиксированная ширина */
}
/* Стили фона (backdrop) для модального режима */
#styledDialog::backdrop {
background-color: rgba(0, 0, 0, 0.5); /* Полупрозрачный темный фон */
}Комментарии:
- селектор
dialog::backdropпозволяет стилизовать фон, который появляется за модальным диалогом; - если вы не зададите стили, браузер покажет дефолтный вид — он может отличаться в разных браузерах.
Центрирование и адаптивность
По умолчанию модальный диалог центрируется браузером автоматически. Если вам нужно управлять позиционированием вручную, можно использовать position:
#styledDialog {
position: fixed; /* Фиксируем относительно окна браузера */
top: 50%;
left: 50%;
transform: translate(-50%, -50%); /* Сдвигаем в центр */
margin: 0; /* Отключаем стандартные margin */
}Но перед тем как делать такую ручную центровку, убедитесь, что стандартное поведение вас не устраивает. Часто хватает чисто декоративных стилей.
Обработка клика по фону (закрытие при клике вне окна)
По умолчанию <dialog> не закрывается при клике по фону (backdrop). Если вам нужно такое поведение, вы можете добавить его сами.
Давайте посмотрим пример:
<dialog id="clickOutsideDialog">
<h2>Диалог с закрытием по клику вне</h2>
<p>Нажмите вне окна чтобы его закрыть</p>
<button id="innerBtn">Кнопка внутри</button>
</dialog>const d = document.getElementById('clickOutsideDialog')
// Открываем где-нибудь в коде
document.getElementById('openOutside').addEventListener('click', () => {
d.showModal()
})
// Обработчик клика по диалогу
d.addEventListener('click', (event) => {
// event.target — элемент, по которому кликнули
// currentTarget — сам dialog
const dialogRect = d.getBoundingClientRect()
// Проверяем, кликнули ли мы вне прямоугольника диалога
const isInDialog =
event.clientX >= dialogRect.left &&
event.clientX <= dialogRect.right &&
event.clientY >= dialogRect.top &&
event.clientY <= dialogRect.bottom
if (!isInDialog) {
d.close('backdrop')
}
})Обратите внимание:
- мы не ставим обработчик на
::backdrop, так как это псевдоэлемент; - вместо этого мы слушаем клики по самому диалогу и проверяем координаты.
Поддержка браузерами и полифиллы
Современные браузеры
На момент актуальности этой статьи <dialog> поддерживается:
- Chrome / Edge / Opera — нативно;
- Firefox — поддержка есть, но может отличаться в некоторых деталях;
- Safari — поддержка есть в современных версиях.
Перед использованием в продакшене стоит проверить актуальное состояние поддержки на caniuse.com, но в целом <dialog> уже можно использовать, особенно если вы готовы подключить полифилл.
Проверка поддержки
Давайте разберемся, как проверить, поддерживается ли <dialog> в браузере:
// Проверяем наличие метода showModal
const isDialogSupported =
typeof HTMLDialogElement === 'function' &&
typeof HTMLDialogElement.prototype.showModal === 'function'
if (!isDialogSupported) {
// Здесь можно подключить полифилл
console.warn('dialog не поддерживается. Подключите полифилл')
}Полифиллы
Полифилл — это скрипт, который имитирует поведение <dialog> в старых браузерах, обычно за счет:
- позиционирования блока;
- накладывания фона;
- перехвата фокуса вручную.
Если вы делаете проект, который должен работать в старых браузерах (например, старые версии Safari), имеет смысл подключить полифилл, например от GoogleChromeLabs (упоминать конкретную ссылку не будем, но вы легко найдете ее по запросу “dialog element polyfill”).
Практический пример: окно подтверждения удаления
Теперь давайте посмотрим, как собрать все сразу в одном примере.
Разметка
<!-- Кнопка удаления элемента -->
<button id="deleteBtn">Удалить элемент</button>
<!-- Диалог подтверждения -->
<dialog id="confirmDialog" aria-labelledby="confirmDialogTitle">
<h2 id="confirmDialogTitle">Подтверждение удаления</h2>
<p>Вы уверены что хотите удалить этот элемент</p>
<form method="dialog">
<!-- Кнопка подтверждения -->
<button value="confirm">Удалить</button>
<!-- Кнопка отмены -->
<button value="cancel">Отмена</button>
</form>
</dialog>Логика на JavaScript
const deleteBtn = document.getElementById('deleteBtn')
const confirmDialog = document.getElementById('confirmDialog')
let lastFocus = null
deleteBtn.addEventListener('click', () => {
// Запоминаем текущий фокус
lastFocus = document.activeElement
// Открываем модально
confirmDialog.showModal()
})
// Обработка результата
confirmDialog.addEventListener('close', () => {
// Возвращаем фокус
if (lastFocus) {
lastFocus.focus()
}
// Смотрим, какую кнопку нажали
if (confirmDialog.returnValue === 'confirm') {
// Здесь выполняем удаление
console.log('Элемент будет удален')
// Например, вызываем функцию удаления
// deleteItem()
} else {
console.log('Удаление отменено')
}
})Как видите, этот код выполняет:
- открытие модального окна;
- возврат фокуса;
- анализ того, какую кнопку нажал пользователь;
- выполнение действия только при явном подтверждении.
Множественные диалоги и вложенные окна
Иногда возникает желание открывать диалог поверх другого диалога. С точки зрения UX это спорное решение, но технически такое возможно.
Последовательное открытие
Рекомендуемый подход — открывать второй диалог только после закрытия первого:
const dialog1 = document.getElementById('dialog1')
const dialog2 = document.getElementById('dialog2')
// Открываем первый
dialog1.showModal()
dialog1.addEventListener('close', () => {
if (dialog1.returnValue === 'next') {
// Если пользователь выбрал «Далее» — открываем второй
dialog2.showModal()
}
})Так вы избегаете сложных схем с несколькими активными модальными окнами.
Одновременные модальные окна
Спецификация не запрещает открывать несколько showModal() подряд, но поведение с точки зрения фокуса и доступности становится значительно сложнее. Лучше этого избегать и вместо этого:
- менять содержимое одного диалога;
- использовать пошаговый мастер (wizard) внутри одного окна.
Типичные ошибки и подводные камни
1. Использование только display: none вместо методов
Некоторые разработчики пытаются «прятать» диалог так же, как любой другой блок: через display: none. Но тогда вы теряете:
- управление фокусом;
- правильные события
cancelиclose; - семантическое поведение модального окна.
Лучше всегда использовать show(), showModal() и close(). CSS-скрытие можно использовать только как дополнение, если вам нужно особое анимационное поведение, но базовое открытие/закрытие должно идти через методы.
2. Отсутствие кнопок управления из клавиатуры
Если вы делаете диалог, у которого нет явной кнопки закрытия или «Отмена», пользователю с клавиатурой придется использовать Esc. Это неудобно. Всегда размещайте в диалоге:
- минимум одну кнопку действия;
- одну кнопку отмены или закрытия.
3. Игнорирование события cancel
Если вы полностью блокируете cancel (Esc), пользователю может быть сложно выйти из окна в некоторых ситуациях. Если вы все же отменяете cancel через preventDefault(), продумайте альтернативный способ закрытия (кнопка «Закрыть»).
4. Никакой связи с элементом, который открыл диалог
Если вы не возвращаете фокус обратно, после закрытия диалога пользователь может оказаться в неожиданном месте страницы. Это особенно заметно на длинных страницах и без мыши. Поэтому почти во всех случаях стоит хранить ссылку на активный элемент и возвращать к нему фокус после закрытия.
Заключение
Элемент <dialog> — это удобный, семантический и довольно гибкий инструмент для создания диалоговых окон в вебе. Он берет на себя большую часть тяжелой работы:
- управление фокусом в модальном режиме;
- семантику для вспомогательных технологий;
- простые методы JavaScript для открытия и закрытия;
- возможность тесной интеграции с формами через
method="dialog"иreturnValue.
При этом от вас требуется:
- аккуратно работать с фокусом (особенно при модальных окнах);
- продумывать UX — наличие понятных кнопок закрытия и подтверждения;
- учитывать поддержку браузерами и при необходимости подключать полифилл;
- не путать
<dialog>с обычным «плавающим блоком» и использовать его именно как диалог.
Если вы будете использовать <dialog> осознанно, то сможете заменить большие плагины и самописные модальные окна более простым, стандартизированным решением.
Частозадаваемые технические вопросы по теме и ответы
Как анимировать появление и скрытие <dialog>
Нативно showModal() и close() не дают анимации. Вы можете:
- Добавить CSS-анимацию или transition для диалога.
- Перед закрытием добавить класс с анимацией, а
close()вызывать в обработчикеanimationendилиtransitionend.
Пример:
dialog.addEventListener('close', () => {
dialog.classList.remove('fade-out') // Сбрасываем класс после закрытия
})
function closeWithAnimation() {
dialog.classList.add('fade-out') // Запускаем анимацию
dialog.addEventListener(
'animationend',
() => dialog.close(),
{ once: true } // Обработчик сработает один раз
)
}Как предотвратить закрытие диалога при клике на кнопку отправки формы
Если у вас форма в диалоге и вы не хотите закрывать окно при сабмите:
- Не используйте
method="dialog". - Добавьте обработчик
submitи вызовитеevent.preventDefault().
form.addEventListener('submit', (event) => {
event.preventDefault() // Диалог не закроется автоматически
// Здесь выполняете свою логику отправки (AJAX и т.п.)
})Как программно определить, открыт ли сейчас <dialog>
Можно проверить наличие атрибута open:
if (dialog.hasAttribute('open')) {
// Диалог открыт
} else {
// Диалог закрыт
}Это надежнее, чем проверка видимости через стили.
Как сделать так, чтобы диалог открывался в определенной позиции (не по центру)
- Используйте
position: fixedилиabsoluteдля диалога. - Отключите автоматическое центрирование, задав свои
top,leftи так далее.
#customPosDialog {
position: fixed;
top: 20px;
right: 20px;
margin: 0;
}Как вложить в диалог компонент с собственной обработкой Esc и не ломать диалог
Если внутренний компонент перехватывает Esc, он может помешать cancel. Решение:
- Внутри компонента не используйте
event.stopPropagation()для Esc без крайней необходимости. - Если нужно, используйте другие сочетания клавиш или явно пробрасывайте событие наружу, вызывая
dialog.close()из компонента, когда пользователь завершает действие.
Постройте личный план изучения Html до уровня Middle — бесплатно!
Html — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Все гайды по Html
Лучшие курсы по теме

HTML и CSS
Антон Ларичев
TypeScript с нуля
Антон Ларичев