Модальные окна: когда они нужны и как сделать правильно

Когда окно оправдано, из чего оно состоит, шторка на телефоне, фокус клавиатуры, окна при входе и встроенный элемент dialog — с живыми примерами.

Интерфейс-элементы Обновлено

Коротко

Модальное окно прерывает человека, поэтому его используют, только когда ответ нужен прямо сейчас: подтвердить удаление, заполнить одно короткое поле, посмотреть товар, не уходя из каталога. Длинные формы, условия и оформление заказа — на страницах, а предложение подписаться — после текста, а не поверх него при входе. Хорошее окно говорит в заголовке, о чём оно, имеет три выхода — крестик, Esc и «Отмена», — держит фокус клавиатуры внутри и возвращает его на кнопку, которая окно открыла. Страница позади приглушена и не прокручивается. На телефоне окно становится шторкой внизу экрана с кнопками во всю ширину под большим пальцем. Большую часть этого встроенный элемент <dialog> делает сам.

Окно или нет

Восемь частых случаев. Окно уместно, когда задача короткая, а ответ нужен сейчас.

  • Подтвердить удаление

    Окно

    Коротко, ответ нужен сейчас, а то, что удаляют, видно позади.

  • Форма «Перезвоните мне» на одно поле

    Окно

    Человек остаётся на странице, которую читал.

  • Быстрый просмотр товара

    Можно

    Экономит переход, но полная страница товара всё равно нужна.

  • Выбор размера или цвета

    Можно

    На телефоне — шторкой снизу.

  • Подписка при входе на страницу

    Не окно

    Человек ещё ничего не прочитал. Лучше работает блок после текста.

  • Оформление заказа

    Не окно

    Страница с шагами: у неё есть кнопка «Назад», и она переживает перезагрузку.

  • Условия и политика

    Не окно

    Длинный текст, которому нужен свой адрес.

  • Сообщение «Сохранено»

    Не окно

    Тихое уведомление, которое исчезает само.

Из чего состоит окно

Шесть частей. Три из них — выходы: крестик, Esc и подложка.

  1. Заголовок — о чём это окно, без «Внимание!»
  2. Крестик — в правом углу, 44 × 44
  3. Содержимое — одна задача, без прокрутки внутри окна
  4. Действия — главное последним, подпись повторяет суть
  5. Подложка — приглушает страницу; клик по ней закрывает окно, если ничего не введено
  6. Esc — закрывает окно с клавиатуры

На телефоне — шторка снизу

Окно в центре экрана телефона далеко от большого пальца. Шторка ставит те же кнопки вниз, во всю ширину.

Компьютер: по центру
Телефон: шторка снизу

В шторке главное действие ставят первым в столбике, а полоска сверху показывает, что шторку можно смахнуть вниз. Если содержимое не помещается в половину экрана, это уже не шторка, а страница.

Фокус остаётся внутри

С клавиатуры Tab ходит по кругу внутри окна. До страницы позади не добраться, а после закрытия фокус возвращается туда, где был.

Без этого тот, кто работает с клавиатуры, уходит из окна в невидимую страницу, а после закрытия оказывается в начале документа. <dialog>, открытый через showModal(), держит фокус внутри, закрывается по Esc и делает страницу позади недоступной — дополнительная библиотека не нужна.

Окно в коде: 3 примера

Встроенный <dialog>: разметка с формой, которая закрывает окно, стили со шторкой для телефона и скрипт открытия и подложки.

Разметка

Форма с method="dialog" закрывает окно; значение нажатой кнопки говорит, как именно.

dialog.html
<button class="button" type="button" data-open="callback">Перезвоните мне</button>

<dialog class="dialog" id="callback" aria-labelledby="callback-title">
  <form class="dialog__body" method="dialog">
    <h2 id="callback-title">Перезвоним вам</h2>
    <button class="dialog__close" value="cancel" formnovalidate aria-label="Закрыть">✕</button>

    <label for="callback-phone">Телефон</label>
    <input id="callback-phone" name="phone" type="tel" autocomplete="tel" required>

    <div class="dialog__actions">
      <button class="button button--ghost" value="cancel" formnovalidate>Отмена</button>
      <button class="button" value="send">Жду звонка</button>
    </div>
  </form>
</dialog>

Стили

Подложка, страница, которая не прокручивается, и шторка на узких экранах.

dialog.css
.dialog {
  width: min(100% - 2rem, 34rem);
  padding: 0;            /* отступы внутри: клик по самому dialog — это клик по подложке */
  border: 0;
  border-radius: var(--radius-lg);
}

.dialog__body {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-5);
}

.dialog::backdrop {
  background: rgb(23 24 23 / 0.6);
}

/* Страница позади не прокручивается */
html:has(.dialog[open]) {
  overflow: hidden;
}

/* На телефоне: шторка снизу, кнопки во всю ширину, главная сверху */
@media (max-width: 47.99rem) {
  .dialog {
    width: 100%;
    max-width: none;
    margin: auto 0 0;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  }

  .dialog__actions {
    display: flex;
    flex-direction: column-reverse;
    gap: var(--space-2);
  }
}

Открытие и подложка

showModal() делает сложную часть; скрипт добавляет закрытие по подложке и возврат фокуса.

dialog.js
// Кнопка открывает своё окно; после закрытия фокус возвращается на кнопку
for (const opener of document.querySelectorAll('[data-open]')) {
  const dialog = document.getElementById(opener.dataset.open);
  opener.addEventListener('click', () => {
    dialog.showModal();
    dialog.addEventListener('close', () => opener.focus(), { once: true });
  });
}

// Клик по подложке закрывает окно — если ничего не введено
for (const dialog of document.querySelectorAll('dialog')) {
  dialog.addEventListener('click', (event) => {
    const typed = [...dialog.querySelectorAll('input, textarea')].some((field) => field.value);
    if (event.target === dialog && !typed) dialog.close('cancel');
  });
}

7 правил модальных окон

  1. 01

    Только когда ответ нужен сейчас

    Всё остальное — страница, блок или уведомление.

  2. 02

    Заголовок говорит, о чём окно

    «Удалить из корзины?», а не «Внимание!».

  3. 03

    Три выхода

    Крестик, Esc и «Отмена»; подложка — если ничего не введено.

  4. 04

    Фокус внутри и обратно

    Tab не уходит из окна, а после закрытия возвращается на кнопку.

  5. 05

    Шторка на телефоне

    Кнопки внизу, во всю ширину, под большим пальцем.

  6. 06

    Одно окно за раз

    Следующий шаг меняет содержимое, а не открывает окно поверх.

  7. 07

    Не при входе

    Окно открывается в ответ на действие человека.

Частые ошибки с модальными окнами

  1. Окно в первую же секунду

    Его закрывают не читая и запоминают раздражение.

  2. Крошечный или запоздалый крестик

    Человек ищет выход вместо того, чтобы читать.

  3. Подложка стирает введённое

    Один промах мимо окна — и форму заполнять заново.

  4. Страница прокручивается позади

    После закрытия человек оказывается в другом месте страницы.

  5. Окно поверх окна

    Непонятно, какое закрывается и куда вернёшься.

  6. Длинная форма в окне

    Прокрутка внутри прокрутки, а на телефоне кнопки не достать.

Вопросы о модальных окнах

Модальное окно или отдельная страница?

Страница — если содержимое длинное, нужен свой адрес или кнопка «Назад». Окно — для короткой задачи, не уходя со страницы.

Должен ли клик по подложке закрывать окно?

Да, если ничего не введено. Если человек начал заполнять форму, промах мимо окна не должен её стирать.

Что даёт элемент dialog?

Открытый через showModal(), он приглушает страницу, держит фокус внутри, закрывается по Esc и понятен экранным читалкам. Его поддерживают все современные браузеры.

Как модальное окно выглядит на телефоне?

Шторкой снизу с кнопками во всю ширину. Если содержимое длиннее половины экрана, лучше сделать страницу.

Может ли окно открываться само?

Только для важных предупреждений, например об окончании сеанса. Всё остальное — в ответ на действие.

Какой ширины делать окно?

Настолько, чтобы содержимое помещалось без прокрутки внутри; для окон с текстом — примерно до 560 px.

Форма

Окна, которые
не раздражают

Проектирую и верстаю интерфейсы, где окна появляются только по делу и закрываются так, как люди ожидают. Расскажите о проекте — отвечу в течение рабочего дня.

Или пишите на [email protected]