🪟 Модальное окно

Подтверждение, форма и боковая панель

Подтверждение действия

Заголовок — вопрос, кнопка — действие. «Да» и «Нет» не говорят, что произойдёт.

Содержимое страницы под окном…

Удалить отчёт?

Отчёт и его выгрузки будут удалены безвозвратно.

<div class="relative h-56 overflow-hidden rounded-xl bg-slate-100 dark:bg-slate-800">
  <div class="p-4 text-sm text-slate-600 dark:text-slate-400">Содержимое страницы под окном…</div>
  <div class="absolute inset-0 z-40 bg-slate-900/50"></div>
  <div class="absolute top-1/2 left-1/2 z-50 w-[calc(100%-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2
              rounded-xl bg-white p-5 shadow-2xl dark:bg-slate-900">
    <h3 class="text-base font-semibold text-slate-900 dark:text-slate-50">Удалить отчёт?</h3>
    <p class="mt-1 text-sm text-slate-600 dark:text-slate-400">Отчёт и его выгрузки будут удалены безвозвратно.</p>
    <div class="mt-5 flex justify-end gap-2">
      <button class="rounded-md px-3 py-1.5 text-sm font-medium text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800">Отмена</button>
      <button class="rounded-md bg-rose-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-rose-700">Удалить</button>
    </div>
  </div>
</div>

Окно с формой и прокруткой

Шапка и футер закреплены, прокручивается только середина — кнопки всегда доступны.

Новый заказ

Заполните обязательные поля

<div class="relative h-64 overflow-hidden rounded-xl bg-slate-100 dark:bg-slate-800">
  <div class="absolute inset-0 z-40 bg-slate-900/50"></div>
  <div class="absolute inset-x-4 top-4 bottom-4 z-50 flex max-w-md flex-col rounded-xl bg-white shadow-2xl sm:inset-x-8 dark:bg-slate-900">
    <div class="flex items-start justify-between gap-3 border-b border-slate-200 p-4 dark:border-slate-700">
      <div>
        <h3 class="text-base font-semibold text-slate-900 dark:text-slate-50">Новый заказ</h3>
        <p class="mt-1 text-sm text-slate-600 dark:text-slate-400">Заполните обязательные поля</p>
      </div>
      <button class="rounded-md p-1 text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800" aria-label="Закрыть">✕</button>
    </div>
    <div class="flex-1 space-y-3 overflow-y-auto p-4">
      <div class="space-y-1.5">
        <label class="block text-sm font-medium text-slate-700 dark:text-slate-200" for="pm-client">Клиент</label>
        <input id="pm-client" class="w-full rounded-md border border-slate-300 bg-transparent px-3 py-2 text-sm dark:border-slate-600 dark:text-slate-100">
      </div>
      <div class="space-y-1.5">
        <label class="block text-sm font-medium text-slate-700 dark:text-slate-200" for="pm-sum">Сумма</label>
        <input id="pm-sum" inputmode="numeric" class="w-full rounded-md border border-slate-300 bg-transparent px-3 py-2 text-sm dark:border-slate-600 dark:text-slate-100">
      </div>
      <div class="space-y-1.5">
        <label class="block text-sm font-medium text-slate-700 dark:text-slate-200" for="pm-note">Комментарий</label>
        <textarea id="pm-note" rows="3" class="w-full rounded-md border border-slate-300 bg-transparent px-3 py-2 text-sm dark:border-slate-600 dark:text-slate-100"></textarea>
      </div>
    </div>
    <div class="flex justify-end gap-2 border-t border-slate-200 p-4 dark:border-slate-700">
      <button class="rounded-md px-3 py-1.5 text-sm font-medium text-slate-600 hover:bg-slate-100 dark:text-slate-300 dark:hover:bg-slate-800">Отмена</button>
      <button class="rounded-md bg-slate-900 px-3 py-1.5 text-sm font-medium text-white dark:bg-slate-100 dark:text-slate-900">Создать</button>
    </div>
  </div>
</div>

Боковая панель

Панель выезжает сбоку: удобнее для фильтров и длинных списков настроек.

<div class="relative h-56 overflow-hidden rounded-xl bg-slate-100 dark:bg-slate-800">
  <div class="absolute inset-0 z-40 bg-slate-900/50"></div>
  <aside class="absolute inset-y-0 right-0 z-50 flex w-64 flex-col gap-4 bg-white p-4 shadow-2xl dark:bg-slate-900">
    <div class="flex items-center justify-between">
      <h3 class="text-base font-semibold text-slate-900 dark:text-slate-50">Фильтры</h3>
      <button class="rounded-md p-1 text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800" aria-label="Закрыть">✕</button>
    </div>
    <label class="flex items-center gap-2 text-sm text-slate-700 dark:text-slate-200"><input type="checkbox" checked class="size-4 accent-sky-600"> Только мои</label>
    <label class="flex items-center gap-2 text-sm text-slate-700 dark:text-slate-200"><input type="checkbox" class="size-4 accent-sky-600"> С вложениями</label>
    <label class="flex items-center gap-2 text-sm text-slate-700 dark:text-slate-200"><input type="checkbox" class="size-4 accent-sky-600"> Архивные</label>
    <button class="mt-auto rounded-md bg-slate-900 px-3 py-2 text-sm font-medium text-white dark:bg-slate-100 dark:text-slate-900">Применить</button>
  </aside>
</div>

Разбор

КлассЗачем
fixed inset-0 z-40подложка на весь экран (в демо — absolute)
bg-slate-900/50затемнение, работающее на любом фоне
top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2центрирование абсолютного окна
max-w-sm w-[calc(100%-2rem)]ширина с полями на мобильном
flex flex-col + overflow-y-autoпрокрутка только содержимого
shadow-2xlсамая крупная тень — окно «выше» всего
aria-label у крестикакнопка без текста должна быть названа

Вариант на shadcn/ui

<Dialog>
  <DialogTrigger render={<Button variant="destructive">Удалить</Button>} />
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Удалить отчёт?</DialogTitle>
      <DialogDescription>Действие нельзя отменить.</DialogDescription>
    </DialogHeader>
    <DialogFooter>
      <DialogClose render={<Button variant="ghost">Отмена</Button>} />
      <Button variant="destructive">Удалить</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

Компонент добавляет то, чего нет в статической разметке: фокус-ловушку, закрытие по Esc, блокировку прокрутки страницы и возврат фокуса.

Частые ошибки

  • Окно без заголовка — скринридер объявит просто «диалог».
  • Нет фокус-ловушки: Tab уводит на страницу под окном.
  • Страница под окном продолжает прокручиваться.
  • Кнопки «Да» и «Нет» вместо названий действий.
  • Длинное содержимое без прокрутки: кнопки уезжают за экран.
  • Модальное окно ради сообщения «Сохранено» — достаточно уведомления.