Модальное окно (modal window) — это окно поверх страницы или приложения, которое блокирует работу с остальным интерфейсом, пока пользователь не ответит или не закроет его. Ключевое слово здесь — «блокирует»: пока окно открыто, кнопки и ссылки под ним не нажимаются.
Содержание
Ниже — чем модальное окно отличается от поп-апа, полный рабочий пример на HTML-элементе <dialog>, требования доступности и запасной вариант на div для старых браузеров. Примеры проверены 30.09.2026 в Chromium 152 (headless-режим).
Модальное окно, поп-ап и диалог: в чем разница
Эти слова часто используют как синонимы, но они описывают разные свойства.
| Термин | Что означает | Блокирует страницу |
|---|---|---|
| Диалог (dialog) | Любое окно для разговора с пользователем: вопрос, форма, сообщение | Зависит от режима |
| Модальное окно | Диалог в модальном режиме: остальной интерфейс недоступен | Да |
| Немодальное окно | Диалог, рядом с которым страницей можно пользоваться (чат поддержки, панель поиска) | Нет |
| Поп-ап (pop-up) | Любой всплывающий элемент: баннер подписки, подсказка, меню, новое окно браузера | Не обязательно |
Отличие поп-апа от модального окна: «поп-ап» говорит о том, как элемент появляется (всплывает), а «модальное» — о том, как он себя ведет (не дает работать с остальным). Рекламный баннер на весь экран — это поп-ап, сделанный модальным. Всплывающая подсказка у иконки — поп-ап, но не модальное окно.
Термин пришел из настольных систем: там модальным называют, например, окно «Сохранить изменения?», которое не дает вернуться к документу, пока не выбран ответ. В Windows так же ведут себя стандартные диалоги «Открыть» и «Сохранить как». Про окна операционной системы — в статье Окно в Windows.
Когда модальное окно уместно
Модальное окно прерывает человека, поэтому оно оправдано, когда без ответа нельзя продолжать:
- подтверждение необратимого действия (удалить, оплатить, выйти без сохранения);
- короткая форма, результат которой нужен текущей странице (вход, выбор адреса);
- критическое сообщение, которое нельзя пропустить.
Для рекламы, подсказок и длинных форм лучше подходят немодальные элементы или отдельная страница. Окно, которое всплывает сразу после загрузки и закрывает контент, мешает читать и раздражает, особенно на телефоне.
Модальное окно на HTML-элементе dialog: полный пример
В HTML есть встроенный элемент <dialog>. Он работает во всех актуальных версиях Chrome, Edge, Firefox и Safari (в Safari — с версии 15.4, в Firefox — с 98, обе вышли в марте 2022 года). Сохраните код в файл modal.html и откройте в браузере.
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Модальное окно на dialog</title>
<style>
dialog {
border: 0;
border-radius: 12px;
padding: 24px;
max-width: min(90vw, 420px);
}
dialog::backdrop {
background: rgb(0 0 0 / 0.5);
}
</style>
</head>
<body>
<button id="open">Очистить корзину</button>
<p id="status">В корзине 3 товара</p>
<dialog id="confirm" aria-labelledby="confirm-title">
<h2 id="confirm-title">Удалить все товары?</h2>
<p>Это действие нельзя отменить.</p>
<form method="dialog">
<button value="cancel" autofocus>Нет</button>
<button value="ok">Да, удалить</button>
</form>
</dialog>
<script>
const dialog = document.getElementById('confirm');
const openBtn = document.getElementById('open');
const statusText = document.getElementById('status');
openBtn.addEventListener('click', () => {
dialog.returnValue = '';
dialog.showModal();
});
dialog.addEventListener('close', () => {
if (dialog.returnValue === 'ok') {
statusText.textContent = 'Корзина пуста';
}
});
</script>
</body>
</html>
Что должно получиться: по кнопке «Очистить корзину» страница затемняется, по центру появляется окно, фокус стоит на кнопке «Нет». Кнопка «Да, удалить» закрывает окно и меняет текст на «Корзина пуста». Кнопка «Нет» и клавиша Esc закрывают окно без изменений. После закрытия фокус возвращается на кнопку «Очистить корзину».
Разбор по частям
dialog.showModal()открывает окно в модальном режиме: оно попадает в верхний слой поверх любыхz-index, а остальная страница перестает реагировать на клики и клавишу Tab.::backdrop— псевдоэлемент-подложка под окном на весь экран. Он есть только у окна, открытого черезshowModal().<form method="dialog">— форма, которая не отправляет данные на сервер, а закрывает окно. Значениеvalueнажатой кнопки попадает вdialog.returnValue.- Событие
closeсрабатывает при любом закрытии: кнопкой, клавишей Esc или вызовомdialog.close(). Перед закрытием по Esc дополнительно приходит событиеcancel. autofocusставит начальный фокус на безопасную кнопку «Нет», чтобы случайный Enter не удалил данные.aria-labelledbyсвязывает окно с заголовком: программа экранного доступа объявит «Удалить все товары?» при открытии.
show() и showModal() — не одно и то же
У элемента два метода открытия, и модальным окно делает только один.
| Свойство | show() |
showModal() |
|---|---|---|
| Страница под окном доступна | да | нет |
Подложка ::backdrop |
нет | есть |
| Закрытие по Esc | нет | да |
| Положение по умолчанию | по центру по горизонтали, по вертикали — там, где элемент стоит в документе; прокручивается вместе со страницей | по центру экрана, при прокрутке остается на месте |
Атрибут open в разметке (<dialog open>) ведет себя как show(): окно видно, но оно немодальное. Поэтому модальное окно открывают из JavaScript.
Ошибка: returnValue остается от прошлого раза
Метод dialog.close() без аргумента закрывает окно, но не меняет returnValue. Если значение не сбросить, в нем останется ответ от предыдущего открытия.
Неверно — окно открывается повторно без сброса:
dialog.showModal();
dialog.close('ok'); // пользователь нажал «Да»
dialog.showModal(); // открыли еще раз
dialog.close(); // закрыли из кода, например по таймеру
console.log(dialog.returnValue);
Фактический результат в консоли:
ok
Обработчик close решит, что пользователь снова подтвердил удаление. Исправление — строка dialog.returnValue = ''; перед showModal(), как в полном примере: тогда после dialog.close() в консоли будет пустая строка.
С клавишей Esc поведение зависит от браузера. Chromium 152 при закрытии по Esc сам записывает в returnValue пустую строку, а в документации MDN описано, что при Esc значение не обновляется. Сброс перед showModal() закрывает оба случая.
Закрытие по клику на подложку и прокрутка
Клик по затемненной области окно не закрывает. Проще всего включить это атрибутом closedby="any" на элементе <dialog>. Атрибут новый: он есть в Chrome и Edge с версии 134 и в Firefox с версии 141, а в Safari на сентябрь 2026 года в стабильных версиях его нет (по таблицам caniuse). Браузер без поддержки атрибут игнорирует, и окно по-прежнему закрывается только кнопками и Esc, поэтому видимая кнопка закрытия обязательна.
Вторая особенность: showModal() блокирует клики по странице, но не ее прокрутку колесом или пальцем. Если фон не должен двигаться, добавьте правило:
html:has(dialog:modal) {
overflow: hidden;
}
Доступность: что проверить руками
Элемент <dialog> берет на себя большую часть работы, но не всю. Короткая проверка без мыши:
- Открыть окно клавишей Enter на кнопке. Фокус должен оказаться внутри окна.
- Нажимать Tab. Фокус ходит по элементам окна и не попадает на ссылки страницы под ним.
- Нажать Esc. Окно закрывается, фокус возвращается на кнопку, с которой его открыли.
- Убедиться, что у окна есть имя:
aria-labelledbyс заголовком илиaria-label. - Убедиться, что есть видимая кнопка закрытия: Esc нет на телефоне.
Роль dialog и признак модальности браузер выставляет сам, добавлять role="dialog" и aria-modal="true" к элементу <dialog> не нужно. Это упрощенный список: полная проверка включает программу экранного доступа (NVDA, VoiceOver) и контраст текста.
Вариант на div, CSS и JavaScript для старых браузеров
Если нужно поддержать браузеры старше 2022 года или в проекте уже есть самодельные окна, модальное поведение собирают вручную. Все, что <dialog> делает сам, здесь пишется кодом.
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Модальное окно на div</title>
<style>
.overlay {
position: fixed;
top: 0; right: 0; bottom: 0; left: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
}
.overlay[hidden] { display: none; }
.modal {
background: #fff;
border-radius: 12px;
padding: 24px;
max-width: 420px;
}
</style>
</head>
<body>
<button id="open">Очистить корзину</button>
<p id="status">В корзине 3 товара</p>
<div class="overlay" id="overlay" hidden>
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="m-title">
<h2 id="m-title">Удалить все товары?</h2>
<button id="no">Нет</button>
<button id="yes">Да, удалить</button>
</div>
</div>
<script>
var overlay = document.getElementById('overlay');
var modal = overlay.querySelector('.modal');
var openBtn = document.getElementById('open');
var noBtn = document.getElementById('no');
var yesBtn = document.getElementById('yes');
function openModal() {
overlay.hidden = false;
noBtn.focus();
document.addEventListener('keydown', onKey);
}
function closeModal() {
overlay.hidden = true;
document.removeEventListener('keydown', onKey);
openBtn.focus();
}
function onKey(event) {
if (event.key === 'Escape') {
closeModal();
return;
}
if (event.key !== 'Tab') return;
var active = document.activeElement;
if (!modal.contains(active)) {
event.preventDefault();
noBtn.focus();
} else if (event.shiftKey && active === noBtn) {
event.preventDefault();
yesBtn.focus();
} else if (!event.shiftKey && active === yesBtn) {
event.preventDefault();
noBtn.focus();
}
}
openBtn.addEventListener('click', openModal);
noBtn.addEventListener('click', closeModal);
yesBtn.addEventListener('click', function () {
document.getElementById('status').textContent = 'Корзина пуста';
closeModal();
});
overlay.addEventListener('click', function (event) {
if (event.target === overlay) closeModal();
});
</script>
</body>
</html>
Поведение то же, что в первом примере, плюс закрытие по клику на затемненный фон. Что здесь сделано вручную:
| Задача | <dialog> + showModal() |
Вариант на div |
|---|---|---|
| Окно поверх страницы | верхний слой браузера | position: fixed и, при необходимости, z-index |
| Блокировка кликов по фону | встроена | подложка .overlay на весь экран |
| Закрытие по Esc | встроено | обработчик keydown |
| Фокус не уходит на страницу | встроено | ручной перехват Tab и Shift+Tab |
| Возврат фокуса | встроен | openBtn.focus() |
| Роль для экранного доступа | встроена | role="dialog" и aria-modal="true" |
Первая ветка в onKey нужна для случая, когда фокус оказался вне окна: после клика по тексту окна активным элементом становится body, и без этой проверки Shift+Tab уводит фокус на кнопку страницы под затемнением.
Граница примера: перехват Tab рассчитан ровно на две кнопки. Если в окне появятся поля и ссылки, первый и последний фокусируемый элемент нужно искать в коде, а не задавать заранее. В готовых библиотеках компонентов эта логика уже есть, но и в них стоит пройти проверку из раздела про доступность.
Если не получилось
- Окно видно сразу при загрузке. В разметке остался атрибут
openили в CSS заданоdialog { display: block; }. Закрытый<dialog>скрывает сам браузер, правилоdisplayэто ломает. - Нет затемнения. Окно открыто через
show()или атрибутомopen. Подложка есть только уshowModal(). - Ошибка при открытии. Вызов
showModal()для окна, уже открытого черезshow(), дает исключениеInvalidStateError. Сначала закройте окно методомclose(). - Окно прижато к краю. Стили сбросили
margin: autoуdialog. Верните его или не переопределяйтеmargin. - Кнопка в форме перезагружает страницу. У формы нет
method="dialog".
Выводы
- Модальное окно блокирует остальной интерфейс до ответа пользователя; поп-ап — любой всплывающий элемент, он может быть и немодальным.
- На сайте модальное окно проще всего сделать элементом
<dialog>и методомshowModal(): подложка::backdrop, закрытие по Esc, удержание и возврат фокуса работают без дополнительного кода. show()и атрибутopenоткрывают то же окно немодально: без подложки и без блокировки страницы.- Перед повторным открытием сбрасывайте
returnValue, иначе послеclose()без аргумента (а в части браузеров и после Esc) в нем останется прошлый ответ. - Вариант на
divтребует вручную написать Esc, работу с фокусом и ARIA-атрибуты; он нужен для старых браузеров и унаследованного кода.
Где применяется / связь с практикой
Модальные окна есть почти в каждом веб-интерфейсе: подтверждение удаления, вход, корзина, просмотр фото. На этой небольшой задаче сходятся три базовые темы фронтенда — разметка, стили и обработка событий, — поэтому ее часто дают как учебное упражнение.
Освойте тему на практике
События, работа с DOM и формами последовательно разбираются на курсе «JavaScript-разработчик. Базовый уровень». Посмотреть формат занятий можно на открытых уроках Otus.
FAQ
Чем <dialog> отличается от атрибута popover?
Элемент с атрибутом popover тоже показывается в верхнем слое, но страницу не блокирует и в режиме по умолчанию (auto) закрывается по клику мимо. Он подходит для меню и подсказок, а для вопроса, требующего ответа, нужен showModal().
Можно ли сделать модальное окно только на CSS, без JavaScript?
Показать и скрыть блок можно через :target или скрытый чекбокс, но Esc, удержание и возврат фокуса так не получить. Такое окно выглядит модальным, но с клавиатуры страница под ним остается доступной.
Чем плохи alert() и confirm()?
Это тоже модальные окна, но их внешний вид и текст кнопок задает браузер, а выполнение скрипта останавливается до ответа. Для интерфейса сайта их заменяют на <dialog>, а alert() оставляют для быстрой отладки.



