иконка discount

Скидка 15% по промокоду

кибер понедельник до 01.12иконка discount
CYBER2025
логотип PurpleSchool
логотип PurpleSchool

Диалоговое окно в 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>

Обратите внимание на две вещи:

  1. method="dialog" говорит браузеру: при нажатии кнопки отправки формы — закрыть диалог.
  2. Атрибут 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() не дают анимации. Вы можете:

  1. Добавить CSS-анимацию или transition для диалога.
  2. Перед закрытием добавить класс с анимацией, а 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 } // Обработчик сработает один раз
  )
}

Как предотвратить закрытие диалога при клике на кнопку отправки формы

Если у вас форма в диалоге и вы не хотите закрывать окно при сабмите:

  1. Не используйте method="dialog".
  2. Добавьте обработчик submit и вызовите event.preventDefault().
form.addEventListener('submit', (event) => {
  event.preventDefault() // Диалог не закроется автоматически
  // Здесь выполняете свою логику отправки (AJAX и т.п.)
})

Как программно определить, открыт ли сейчас <dialog>

Можно проверить наличие атрибута open:

if (dialog.hasAttribute('open')) {
  // Диалог открыт
} else {
  // Диалог закрыт
}

Это надежнее, чем проверка видимости через стили.

Как сделать так, чтобы диалог открывался в определенной позиции (не по центру)

  1. Используйте position: fixed или absolute для диалога.
  2. Отключите автоматическое центрирование, задав свои top, left и так далее.
#customPosDialog {
  position: fixed;
  top: 20px;
  right: 20px;
  margin: 0;
}

Как вложить в диалог компонент с собственной обработкой Esc и не ломать диалог

Если внутренний компонент перехватывает Esc, он может помешать cancel. Решение:

  1. Внутри компонента не используйте event.stopPropagation() для Esc без крайней необходимости.
  2. Если нужно, используйте другие сочетания клавиш или явно пробрасывайте событие наружу, вызывая dialog.close() из компонента, когда пользователь завершает действие.
Стрелочка влевоКонтекстное меню в HTML - menuРаскрывающийся блок в HTML - detailsСтрелочка вправо

Постройте личный план изучения Html до уровня Middle — бесплатно!

Html — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Все гайды по Html

Тег section в HTML - семантическая разметка структуры страницыТег nav в HTML - полное руководство по семантической навигацииТег main в HTML - подробное руководство по использованиюТег header в HTML - полное практическое руководствоТег footer в HTML - назначение семантика и практические примерыТег figure в HTML - как правильно оформлять иллюстрации и подписиТег figcaption в HTML - подробное руководство с примерамиТег aside в HTML - назначение правильная семантика и примеры
Текстовая область HTML textarea - практическое руководствоВыпадающий список HTML select - полное руководство для разработчиковОпция списка HTML option - как работает и как правильно использоватьАтрибут method в HTML - как правильно отправлять данные формыЗаголовок группы HTML legend - как правильно использовать и оформлятьТег input в HTML - типы атрибуты валидация и примерыТег формы form в HTMLГруппа полей HTML fieldsetАтрибут action в HTML - как правильно задавать адрес отправки формы
Открыть базу знаний

Лучшие курсы по теме

изображение курса

HTML и CSS

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий