🪟 Модальное окно
Подтверждение действия
Заголовок — вопрос, кнопка — действие. «Да» и «Нет» не говорят, что произойдёт.
Содержимое страницы под окном…
Удалить отчёт?
Отчёт и его выгрузки будут удалены безвозвратно.
<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 уводит на страницу под окном.
- Страница под окном продолжает прокручиваться.
- Кнопки «Да» и «Нет» вместо названий действий.
- Длинное содержимое без прокрутки: кнопки уезжают за экран.
- Модальное окно ради сообщения «Сохранено» — достаточно уведомления.