dialog
<dialog>-- нативный HTML-элемент для модальных и немодальных диалоговых окон. Автоматически управляет фокусом, backdrop, закрытием по Escape и доступностью.
Зачем нужно
До <dialog> каждый разработчик писал модалки вручную: overlay, z-index, focus trap, закрытие по Escape, блокировка скролла. <dialog> решает всё это нативно. Браузер сам создаёт backdrop, ловит фокус внутри диалога, закрывает по Escape, блокирует взаимодействие с фоном.
Где используется
- Модальные окна подтверждения (удаление, выход)
- Формы в диалогах (логин, настройки)
- Информационные попапы
- Lightbox для изображений
Предпосылки
Базовый синтаксис
<!-- Dialog по умолчанию скрыт -->
<dialog id="my-dialog">
<h2>Заголовок диалога</h2>
<p>Содержимое диалога</p>
<button id="close-btn">Закрыть</button>
</dialog>
<button id="open-btn">Открыть диалог</button>
<script>
const dialog = document.getElementById('my-dialog');
const openBtn = document.getElementById('open-btn');
const closeBtn = document.getElementById('close-btn');
openBtn.addEventListener('click', () => {
dialog.showModal; // Модальный (блокирует фон)
});
closeBtn.addEventListener('click', () => {
dialog.close(); // Закрыть
});
</script>
showModal vs show
// showModal -- МОДАЛЬНЫЙ диалог
dialog.showModal;
// - Блокирует взаимодействие с фоном
// - Показывает ::backdrop
// - Focus trap (Tab не выходит за диалог)
// - Закрытие по Escape
// - Элемент на top layer (поверх всего)
// show -- НЕМОДАЛЬНЫЙ диалог
dialog.show;
// - Фон остаётся интерактивным
// - Нет ::backdrop
// - Нет focus trap
// - Нет закрытия по Escape
// - Просто видимый элемент
close и returnValue
<dialog id="confirm-dialog">
<h2>Удалить файл?</h2>
<p>Это действие необратимо.</p>
<form method="dialog">
<!-- method="dialog" закрывает диалог, значение кнопки → returnValue -->
<button value="cancel">Отмена</button>
<button value="confirm">Удалить</button>
</form>
</dialog>
<script>
const dialog = document.getElementById('confirm-dialog');
dialog.addEventListener('close', () => {
console.log(dialog.returnValue); // "cancel" или "confirm"
if (dialog.returnValue === 'confirm') {
deleteFile;
}
});
// Программное закрытие с returnValue
dialog.close('custom-value');
</script>
::backdrop -- стилизация фона
/* ::backdrop -- псевдоэлемент фона за модальным диалогом */
dialog::backdrop {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
/* Анимация backdrop */
dialog::backdrop {
background: rgba(0, 0, 0, 0);
transition: background 0.3s ease;
}
dialog[open]::backdrop {
background: rgba(0, 0, 0, 0.5);
}
Стилизация dialog
dialog {
border: none;
border-radius: 12px;
padding: 24px;
max-width: 480px;
width: 90%;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
/* Анимация появления */
dialog {
opacity: 0;
transform: translateY(-20px) scale(0.95);
transition: opacity 0.3s ease, transform 0.3s ease,
display 0.3s ease allow-discrete,
overlay 0.3s ease allow-discrete;
}
dialog[open] {
opacity: 1;
transform: translateY(0) scale(1);
}
/* Starting style для анимации открытия (Chrome 117+) */
@starting-style {
dialog[open] {
opacity: 0;
transform: translateY(-20px) scale(0.95);
}
}
Закрытие по клику на backdrop
dialog.addEventListener('click', (e) => {
// Клик на сам dialog (backdrop) -- закрыть
// Клик на содержимое -- не закрывать
if (e.target === dialog) {
dialog.close();
}
});
Альтернативный подход с wrapper:
<dialog id="modal">
<div class="dialog-content">
<h2>Заголовок</h2>
<p>Содержимое</p>
</div>
</dialog>
dialog {
padding: 0;
background: transparent;
}
.dialog-content {
padding: 24px;
background: white;
border-radius: 12px;
}
Полный пример: форма в диалоге
<dialog id="login-dialog" aria-labelledby="login-title">
<form method="dialog" id="login-form">
<h2 id="login-title">Вход в аккаунт</h2>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required autofocus>
<label for="password">Пароль:</label>
<input type="password" id="password" name="password" required>
<div class="dialog-actions">
<button type="button" id="cancel-login">Отмена</button>
<button type="submit" value="login">Войти</button>
</div>
</form>
</dialog>
<script>
const loginDialog = document.getElementById('login-dialog');
const loginForm = document.getElementById('login-form');
document.getElementById('login-btn').addEventListener('click', () => {
loginDialog.showModal;
});
document.getElementById('cancel-login').addEventListener('click', () => {
loginDialog.close('cancel');
});
loginForm.addEventListener('submit', (e) => {
// method="dialog" закроет диалог
const formData = new FormData(loginForm);
handleLogin(formData);
});
loginDialog.addEventListener('close', () => {
loginForm.reset(); // Очистить форму при закрытии
});
</script>
Доступность dialog
<!-- aria-labelledby для заголовка -->
<dialog aria-labelledby="dialog-title">
<h2 id="dialog-title">Подтверждение</h2>
...
</dialog>
<!-- aria-describedby для описания -->
<dialog aria-labelledby="title" aria-describedby="desc">
<h2 id="title">Удаление</h2>
<p id="desc">Файл будет удалён безвозвратно.</p>
...
</dialog>
<dialog> автоматически:
- Устанавливает
role="dialog" - Создаёт focus trap при
showModal - Закрывается по Escape
- Возвращает фокус на элемент, который открыл диалог
Частые ошибки
| Ошибка | Проблема | Решение |
|---|---|---|
dialog.style.display = 'block' |
Нет focus trap и backdrop | Использовать showModal |
Нет aria-labelledby |
SR не знает заголовок | Связать с <h2> |
| Нет кнопки закрытия | Только Escape для закрытия | Добавить явную кнопку |
autofocus на неправильном элементе |
Фокус не на первом поле | autofocus на первом input |
| Не очищать форму при закрытии | Старые данные при повторном открытии | form.reset() в close handler |
Практика
- Создай модальное окно подтверждения с
showModalиreturnValue - Стилизуй
::backdropс blur-эффектом - Реализуй закрытие по клику на backdrop
- Создай форму в диалоге с валидацией
- Добавь анимацию открытия/закрытия через CSS transitions
Связанные темы
- ARIA атрибуты --
role="dialog",aria-modal - Фокус и клавиатурная навигация -- focus trap
- details и summary -- другой нативный интерактивный элемент
- Валидация форм -- формы внутри dialog