SweetAlert2: диалоги для веб-сервисов — мастерская и 6 примеров
Мини-мастерская SweetAlert2 и шесть рабочих интерфейсов: подтверждение удаления, переименование, отправка формы, повтор после ошибки, пошаговая заявка и сохранение с отменой. Живые диалоги, пояснения и исходники.

Удалить выбранные заявки, переименовать проект, дождаться проверки формы, повторить неудачную операцию — небольшие диалоги встречаются почти в каждом сервисе. От их поведения зависит, понимает ли человек последствия действия и может ли спокойно исправить ошибку.
SweetAlert2 помогает строить такие окна на обычном JavaScript: с понятными кнопками, полями, состоянием ожидания и результатом, который удобно обработать через await. Здесь доступны мини-мастерская и шесть живых интерфейсов, исходники и самостоятельный HTML. Демонстрации используют SweetAlert2 11.26.25.
Чем SweetAlert2 отличается от alert, confirm и prompt
Нативные диалоги подходят для короткой проверки в консоли или простого служебного действия. Они встроены в браузер, но почти не дают управлять видом, подписями и содержимым. У confirm() результат — логическое значение, у prompt() — строка либо null при отмене. Эти вызовы синхронные: следующая строка кода ждёт завершения диалога.
const accepted = window.confirm('Удалить выбранные записи?');
if (accepted) {
console.log('Пользователь подтвердил действие');
}
const name = window.prompt('Название проекта', 'Личный кабинет');
if (name !== null) console.log(name);
Swal.fire() возвращает Promise. Окно открывается, а приложение продолжает работать. await приостанавливает конкретную асинхронную функцию, затем отдаёт ей объект результата. Поэтому заменять confirm() механическим поиском по проекту нельзя: нужно перестроить место, где принимается решение.
async function askBeforeDelete() {
const result = await Swal.fire({
titleText: 'Удалить выбранные записи?',
text: 'Проверьте список перед подтверждением.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Удалить',
cancelButtonText: 'Отмена',
focusCancel: true
});
return result.isConfirmed;
}
| Задача | Нативный диалог | SweetAlert2 |
|---|---|---|
| Подтверждение | confirm возвращает true/false | Отдельные результаты Confirm, Deny и закрытие |
| Ввод | Одна строка prompt | Поле с подписью, проверкой и сообщением об ошибке |
| Ожидание запроса | Отдельный интерфейс приходится писать рядом | preConfirm, loader и контролируемое закрытие |
| Оформление и мобильный экран | Управляются браузером | Стили, размеры и поведение контролирует приложение |
SweetAlert и SweetAlert2 — разные пакеты. Для этой статьи нужен sweetalert2 и вызов Swal.fire. Не смешивайте примеры с swal(...) из другой библиотеки. При этом не каждое действие нуждается в модальном окне: постоянную настройку обычно удобнее менять прямо на странице, а короткий результат можно показать уведомлением.
Источник: поведение нативного confirm.
Источник: тип Promise и объект результата SweetAlert2.
Мини-мастерская: соберите собственный диалог
В мастерской четыре исходных варианта: подтверждение, ввод, toast и сообщение об ошибке. Меняйте заголовок, описание, подписи кнопок и доступные параметры, затем открывайте настоящий диалог. Рядом появляется его результат: подтверждение, отмена, причина закрытия и введённое значение. Так проще понять связь между настройкой и поведением.
Первое упражнение: откройте «Сохранить настройки?» и нажмите «Отмена». В журнале будут isConfirmed: false, isDismissed: true и dismiss: "cancel". При повторном запуске нажмите «Сохранить»: подтверждение станет истинным. Настройки на сервер при этом не отправляются.
Затем выберите ввод названия проекта. Пустое значение должно оставить окно открытым с пояснением. Введите «Личный кабинет» — строка попадёт в результат. В toast проверьте закрытие по таймеру и паузу, когда уведомление находится под указателем или клавиатурным фокусом. Вариант ошибки показывает сообщение; сетевого запроса за ним нет.
Можно получить конфигурацию JavaScript с обработчиками, JSON настроек и полный HTML. Это разные результаты: JSON хранит значения, но не исполняемые функции; JavaScript показывает настройку библиотеки; самостоятельный HTML содержит всё необходимое для работы примера. После сброса сохранённый HTML возвращается к варианту, который был экспортирован, а журнал начинается заново.
Экспериментируйте по одному параметру: сначала поменяйте подпись, затем поведение закрытия, затем режим. Если изменить всё сразу, трудно понять причину нового результата. После экспорта откройте файл заново и повторите действие: рабочий исходник полезнее снимка, который показывает только внешний вид окна.
Шесть интерфейсов: попробуйте полный путь пользователя
Ниже каждый диалог связан с небольшой рабочей задачей. Смотрите на состояние до открытия, результат после подтверждения и путь отмены. Переключение примеров помогает сравнить, когда уместен вопрос, когда нужен ввод, а когда достаточно ненавязчивого уведомления.
Данные вымышленные и хранятся в текущем примере. Ожидание и ошибки в асинхронных сценариях воспроизводятся локально: заявки, письма и платежи никуда не отправляются. Реальный fetch и серверная проверка отдельно разобраны ниже, чтобы можно было перенести подход в свой проект.
Скачать исходники мастерской и шести примеров. У каждого сценария есть самостоятельный HTML для экспериментов. Меняйте данные, проверяйте результат, затем используйте подход в своём интерфейсе.
JSON из интерфейса сохраняет данные сценария, а HTML — ещё и возможность взаимодействовать с ними. Проверьте обе стороны переноса: в файле должны остаться введённые значения, а сброс должен вернуть именно экспортированный вариант. Журнал демонстрации не является историей серверных операций и не подтверждает, что данные были записаны в базу.
1. Подтверждение удаления: контекст важнее слова «Да»
В списке заявок человек должен понимать, что именно исчезнет. Покажите количество выбранных записей, назовите действие глаголом и оставьте безопасный путь назад. Кнопки «Удалить» и «Отмена» читаются быстрее, чем одинаково общие «Да» и «Нет».
Попробуйте: в исходном списке четыре задачи. Выберите «Проверить мобильную форму» и «Добавить фильтр», откройте подтверждение и сначала отмените его — останутся все четыре. Повторите с подтверждением: останутся две задачи. Восстановление вернёт четыре записи и прежний выбор.
async function confirmRemoval(ids) {
if (ids.length === 0) return false;
const result = await Swal.fire({
titleText: `Удалить записи: ${ids.length}?`,
text: 'Будут затронуты только выбранные строки.',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Удалить',
cancelButtonText: 'Отмена',
focusCancel: true,
allowOutsideClick: false
});
return result.isConfirmed;
}
Библиотека показывает вопрос и возвращает решение. Получение выбранных ID, удаление, восстановление и пересчёт счётчиков — логика приложения. Снимок выбранных идентификаторов лучше сделать до открытия окна: иначе список успеет измениться, а пользователь подтвердит уже другой набор. В настоящем API сервер дополнительно проверяет права и существование каждой записи.
Есть и второй момент: пока пользователь читает вопрос, другая вкладка или оператор может изменить запись. Поэтому подтверждение относится к намерению, а не гарантирует успешное выполнение. После ответа API обновляйте только действительно изменённые строки и отдельно объясняйте частичный отказ. В учебном массиве такого конфликта нет, но границу полезно заложить сразу.
Открыть подтверждение удаления ↑
2. Переименование: ввод, проверка и понятная ошибка
Для короткого названия отдельный экран часто избыточен. Диалог с заполненным текущим значением позволяет исправить его в контексте карточки. Подпись поля нужна даже при наличии placeholder: подсказка исчезает после ввода, а назначение поля должно оставаться понятным.
Попробуйте: проект изначально называется «Личный кабинет». Отправьте пустую строку или только пробелы: окно останется открытым с пояснением. Затем задайте «Кабинет партнёра» и сравните заголовок карточки с результатом. В этом интерфейсе допустимы от 3 до 60 символов после удаления пробелов по краям.
async function askProjectName(currentName) {
return Swal.fire({
titleText: 'Переименовать проект',
input: 'text',
inputLabel: 'Название проекта',
inputValue: currentName,
inputAttributes: { maxlength: '60', autocomplete: 'off' },
showCancelButton: true,
confirmButtonText: 'Сохранить',
cancelButtonText: 'Отмена',
inputValidator: value => {
const name = value.trim();
if (name.length < 3) return 'Введите название от 3 символов';
if (name.length > 60) return 'Максимум 60 символов';
}
});
}
// После await: менять модель только если result.isConfirmed.
inputValidator возвращает сообщение об ошибке; отсутствие сообщения разрешает продолжение. Простое ограничение длины не проверяет уникальность имени и не сохраняет его на сервер. Если нужна асинхронная проверка, покажите ожидание и обработайте отказ так, чтобы введённое значение не исчезло.
Открыть переименование проекта ↑
3. Асинхронное действие: подтверждение, ожидание и результат
Между нажатием и ответом сервиса проходит время. Если закрыть окно сразу и показать успех заранее, человек получит ложное подтверждение. Лучше оставить контекст, показать выполнение и завершить сценарий после результата операции.
Попробуйте: отправьте заполненную форму с темой, приоритетом и описанием. После подтверждения появится ожидание на 650 мс, затем квитанция DEMO-201. Измените тему и отправьте снова — получится DEMO-202. Ни одного сетевого запроса здесь нет; квитанция относится только к локальному примеру.
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
async function demonstrateCheck() {
return Swal.fire({
titleText: 'Проверить данные?',
text: 'Локальная демонстрация ожидания, без отправки.',
showCancelButton: true,
confirmButtonText: 'Проверить',
cancelButtonText: 'Отмена',
showLoaderOnConfirm: true,
allowOutsideClick: () => !Swal.isLoading(),
allowEscapeKey: () => !Swal.isLoading(),
preConfirm: async () => {
await wait(800);
return { checked: true, mode: 'local-demo' };
}
});
}
preConfirm связывает подтверждение с асинхронной функцией. Её результат станет result.value. Библиотека управляет ожиданием внутри окна, а приложение решает, когда операция считается законченной и какой статус показать на странице. Для длительных задач удобнее отдельная страница прогресса с возможностью вернуться позже.
В диалоге подтверждения полезно повторить самые важные поля формы. Это позволяет заметить ошибочную тему или приоритет до отправки. После операции оставьте квитанцию на странице: пользователь сможет перечитать её после закрытия окна. Один быстро исчезнувший экран успеха для такого сценария недостаточен.
4. Ошибка и повтор: не теряйте введённые данные
Временный сбой не должен заставлять человека начинать заново. Сохраните контекст и объясните, что доступно: исправить значение, повторить запрос или отменить действие. Не превращайте любую ошибку в новое окно поверх старого — иначе теряются ввод, фокус и связь с исходной задачей.
Попробуйте: нажмите «Загрузить данные». Первая попытка специально завершится ошибкой. Выберите «Повторить»: вторая даст три записи. Сброс вернёт сценарий к первой ошибке. В интерфейсе это последовательность отдельных окон ожидания, ошибки и результата. Фрагмент ниже показывает другой полезный приём: повтор проверки внутри одного открытого окна.
async function demonstrateRetry() {
let attempt = 0;
return Swal.fire({
titleText: 'Проверка соединения',
text: 'Первая попытка в этой демонстрации завершится ошибкой.',
confirmButtonText: 'Проверить',
showCancelButton: true,
cancelButtonText: 'Отмена',
showLoaderOnConfirm: true,
allowOutsideClick: () => !Swal.isLoading(),
allowEscapeKey: () => !Swal.isLoading(),
preConfirm: async () => {
await new Promise(resolve => setTimeout(resolve, 600));
attempt += 1;
if (attempt === 1) {
Swal.showValidationMessage('Не получилось. Попробуйте ещё раз.');
return false;
}
return { attempt, checked: true };
}
});
}
return false оставляет диалог открытым. Это отличается от возврата объекта с полем ok: false: такой объект сам по себе не запрещает закрытие. В реальном приложении возможность повтора зависит от операции. Проверить статус обычно можно повторно; создать платёж или отправить письмо следует с серверной защитой от дублирования.
5. Пошаговый мастер: собираем решение до сохранения
Несколько коротких вопросов удобно объединить в последовательность: тип заявки, описание и итоговое подтверждение. Но окно не должно превращаться в длинную анкету с десятками полей. Для сложной работы лучше обычная форма, где видны все разделы и ошибки.
Попробуйте: мастер содержит три шага: тема, приоритет и описание. На втором выберите «Высокий», вернитесь назад и измените тему. Закройте мастер, откройте снова и завершите его — обращение W-401 сохранит выбранные значения. До завершения сохраняются черновик и шаг, но готовое обращение ещё не создаётся.
async function twoStepRequest() {
const nameStep = await Swal.fire({
titleText: 'Название заявки',
input: 'text', inputLabel: 'Короткое название',
showCancelButton: true,
confirmButtonText: 'Далее', cancelButtonText: 'Отмена',
progressSteps: ['1', '2'], currentProgressStep: 0,
inputValidator: value => !value.trim() && 'Введите название'
});
if (!nameStep.isConfirmed) return null;
const review = await Swal.fire({
titleText: 'Проверьте заявку',
text: nameStep.value,
showCancelButton: true,
confirmButtonText: 'Создать', cancelButtonText: 'Отмена',
progressSteps: ['1', '2'], currentProgressStep: 1
});
return review.isConfirmed ? { name: nameStep.value } : null;
}
Это минимальный двухшаговый фрагмент. Полный интерфейс хранит черновик и текущий шаг отдельно, поэтому умеет возвращаться назад. progressSteps лишь рисует индикатор: переходы, сохранение введённых значений и момент окончательного создания записи реализует приложение. Не сохраняйте половину заявки только потому, что первый вопрос был подтверждён.
6. Toast и отмена: сообщаем результат без остановки работы
Небольшое уведомление подходит для обратимого изменения: настройка сохранена, карточка перемещена, метка добавлена. Toast не блокирует страницу и не получает автоматический фокус. Пользователь может продолжать работу, поэтому критичный вопрос нельзя прятать в быстро исчезающем сообщении.
Попробуйте: переключите вид «Список» на «Карточки» и сохраните. Уведомление показывается 4,5 секунды; «Отменить» вернёт «Список». После исчезновения toast остаётся постоянная кнопка восстановления. Для следующего сохранения нужен новый снимок, иначе отмена вернёт слишком старые данные.
const Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
showCloseButton: true,
closeButtonAriaLabel: 'Закрыть уведомление',
timer: 5000,
timerProgressBar: true
});
function announceSaved() {
return Toast.fire({ icon: 'success', titleText: 'Изменения сохранены' });
}
Пауза таймера нужна и при наведении, и при фокусе внутри уведомления. Эти состояния могут пересекаться: уход указателя не должен запустить отсчёт, если человек ещё работает с кнопкой с клавиатуры. Такой обработчик можно добавить в настройки toast:
function pauseWhileReading(popup) {
let hovered = false;
const syncTimer = () => {
const focused = popup.contains(document.activeElement);
if (hovered || focused) Swal.stopTimer();
else Swal.resumeTimer();
};
popup.addEventListener('mouseenter', () => { hovered = true; syncTimer(); });
popup.addEventListener('mouseleave', () => { hovered = false; syncTimer(); });
popup.addEventListener('focusin', syncTimer);
popup.addEventListener('focusout', () => queueMicrotask(syncTimer));
}
// В опциях toast: didOpen: pauseWhileReading
// Swal.getTimerLeft() вернёт оставшиеся миллисекунды или undefined.
Библиотека показывает уведомление и отсчитывает время. Восстановление данных, история изменений и срок доступности отмены — отдельные решения приложения. Плашка «Сохранено» должна появляться после действительного успеха, а не просто после нажатия кнопки.
Открыть уведомление с отменой ↑
Подключение: готовая HTML-страница, npm и локальные файлы
Для обычной страницы достаточно CSS и JavaScript. Пример ниже можно сохранить как index.html и открыть в браузере при доступе к CDN. Кнопка меняет текст результата только после решения пользователя. Это полный минимальный документ; остальные фрагменты статьи показывают отдельные приёмы.
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Первый диалог SweetAlert2</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11.26.25/dist/sweetalert2.min.css">
</head>
<body class="p-4">
<main class="container">
<h1 class="h3">Настройки проекта</h1>
<button id="open-dialog" type="button" class="btn btn-primary">Сохранить</button>
<p id="result" class="mt-3" role="status">Изменения ещё не подтверждены.</p>
</main>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11.26.25/dist/sweetalert2.min.js"></script>
<script>
const button = document.getElementById('open-dialog');
const output = document.getElementById('result');
button.addEventListener('click', async () => {
button.disabled = true;
try {
const result = await Swal.fire({
titleText: 'Применить настройки?',
text: 'Это локальный пример без отправки на сервер.',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Применить',
cancelButtonText: 'Отмена',
returnFocus: false
});
output.textContent = result.isConfirmed
? 'Настройки подтверждены в этом примере.'
: 'Изменения не применены.';
} finally {
button.disabled = false;
button.focus();
}
});
</script>
</body>
</html>
В проекте со сборщиком установите пакет и используйте импорт. Здесь указан точный номер для воспроизводимости; обновление зависимости делайте осознанно вместе с проверкой сценариев.
npm install sweetalert2@11.26.25
import Swal from 'sweetalert2';
document.querySelector('#help').addEventListener('click', () => {
Swal.fire({ titleText: 'Подсказка', text: 'Выберите нужную строку.', icon: 'info' });
});
CDN необязателен: положите файлы библиотеки в свой проект и замените адреса. Вариант sweetalert2.min.js используется с отдельным CSS; сборка sweetalert2.all.min.js включает стили. Не подключайте оба варианта одновременно. Bare import из npm требует сборщика или корректной карты импортов и не заработает от простой вставки в обычный <script>.
<link rel="stylesheet" href="./vendor/sweetalert2.min.css">
<script src="./vendor/sweetalert2.min.js"></script>
<script src="./app.js"></script>
Источник: релиз и файлы SweetAlert2 11.26.25.
Результат, три действия и повторное использование настроек
Подтверждение, альтернативное действие и закрытие имеют разные смыслы. Например, «Сохранить», «Не сохранять» и «Вернуться к редактированию» нельзя свести к одному логическому значению. isDenied означает нажатие отдельной кнопки Deny; «Отмена», Escape и крестик относятся к закрытию.
async function askAboutChanges() {
const result = await Swal.fire({
titleText: 'Сохранить изменения перед выходом?',
showDenyButton: true, showCancelButton: true,
confirmButtonText: 'Сохранить',
denyButtonText: 'Не сохранять',
cancelButtonText: 'Продолжить редактирование'
});
if (result.isConfirmed) return 'save';
if (result.isDenied) return 'discard';
return 'stay';
}
Не проверяйте только if (result.value): ноль, пустая строка и false могут быть допустимыми значениями вашей задачи. Сначала выясните, подтвердил ли человек действие, затем проверяйте содержимое. Для диагностики можно различать конкретную причину закрытия:
function describeDismissal(result) {
if (!result.isDismissed) return '';
if (result.dismiss === Swal.DismissReason.cancel) return 'Нажата отмена';
if (result.dismiss === Swal.DismissReason.esc) return 'Нажат Escape';
if (result.dismiss === Swal.DismissReason.timer) return 'Истёк таймер';
return 'Окно закрыто';
}
Единые подписи и стиль удобно вынести в mixin. Конкретный вызов уточняет общие настройки. Это помогает выдержать один язык интерфейса, но не создаёт независимую систему параллельных модальных окон: следующий вызов может заменить предыдущий.
const Dialog = Swal.mixin({
confirmButtonText: 'Продолжить',
cancelButtonText: 'Отмена',
buttonsStyling: false,
customClass: {
confirmButton: 'btn btn-primary m-1',
cancelButton: 'btn btn-outline-secondary m-1',
denyButton: 'btn btn-outline-danger m-1'
}
});
// Dialog.fire({ titleText: 'Сохранить?', showCancelButton: true });
Отдельно защитите инициатор от повторного открытия, особенно если событие приходит из формы, карточки и горячей клавиши. Флаг занятости сбрасывают в finally, чтобы ошибка не заблокировала действие навсегда. Отключение кнопки — визуальная помощь, а не замена такой проверки.
let dialogBusy = false;
async function openOnce(options) {
if (dialogBusy) return null;
dialogBusy = true;
try {
return await Swal.fire(options);
} finally {
dialogBusy = false;
}
}
Настоящий fetch и PHP: где заканчивается диалог
В рабочем сервисе preConfirm может отправлять запрос. Проверяйте HTTP-статус, формат ответа и смысл результата: fetch не отклоняет Promise только из-за ответа 404 или 500. Сообщение пользователю выбирайте сами, не вставляйте произвольный HTML из серверной ошибки.
Ниже — пример проверки названия через ваш будущий /validate-project.php. Он не вызывается живыми демонстрациями статьи. Тайм-аут ограничивает ожидание; закрытие окна само по себе не отменяет запрос и не откатывает действие на сервере.
async function validateProjectTitle() {
return Swal.fire({
titleText: 'Проверить название',
input: 'text', inputLabel: 'Название проекта',
inputValidator: value => !value.trim() && 'Введите название',
showCancelButton: true,
confirmButtonText: 'Проверить', cancelButtonText: 'Отмена',
showLoaderOnConfirm: true,
allowOutsideClick: () => !Swal.isLoading(),
allowEscapeKey: () => !Swal.isLoading(),
preConfirm: async value => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 8000);
try {
const response = await fetch('/validate-project.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({ title: value.trim() }),
signal: controller.signal
});
if (response.status === 422) {
Swal.showValidationMessage('Проверьте название: от 1 до 60 символов.');
return false;
}
if (!response.ok) throw new Error('HTTP error');
const body = await response.json();
if (body.valid !== true || typeof body.title !== 'string') {
throw new Error('Unexpected response');
}
return body;
} catch (error) {
Swal.showValidationMessage(error.name === 'AbortError'
? 'Сервер не ответил вовремя. Повторите проверку.'
: 'Не удалось проверить название. Попробуйте позже.');
return false;
} finally {
clearTimeout(timeout);
}
}
});
}
Минимальный PHP-обработчик ниже только проверяет строку и возвращает её нормализованное значение. Он не создаёт проект и не пишет в базу. Потребуются PHP 8.1+ и расширение mbstring. Сохраните файл под указанным именем и запускайте вместе со своей HTML-страницей на одном локальном сервере.
<?php
declare(strict_types=1);
header('Content-Type: application/json; charset=utf-8');
function respond(int $status, array $body): never {
http_response_code($status);
echo json_encode($body, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Allow: POST');
respond(405, ['error' => 'method_not_allowed']);
}
try {
$body = json_decode(file_get_contents('php://input'), true, 32, JSON_THROW_ON_ERROR);
} catch (JsonException $error) {
respond(400, ['error' => 'invalid_json']);
}
$title = is_array($body) && is_string($body['title'] ?? null)
? trim($body['title']) : '';
if ($title === '' || mb_strlen($title, 'UTF-8') > 60) {
respond(422, ['error' => 'invalid_title']);
}
respond(200, ['valid' => true, 'title' => $title]);
Для настоящего сохранения добавляются аутентификация, права, защита от CSRF при cookie-сессии и операция в хранилище. Только после подтверждённого сервером изменения можно писать «Сохранено». Если PHP обращается к другому сервису, полезен Guzzle; установка серверных пакетов разобрана в руководстве по Composer. Самой SweetAlert2 Composer не нужен.
Тайм-аут означает, что браузер перестал ждать, но не доказывает, что сервер ничего не сделал. Для проверки названия это просто повод повторить запрос. Для создания заказа нужен другой договор: уникальный ключ операции, получение её текущего статуса и повтор без дублирования. Этот договор проектируют на сервере; библиотека диалогов не может обеспечить его одной настройкой.
Источник: проверка HTTP-ответа и отмена fetch.
Источник: разбор JSON в PHP.
Безопасный текст, свой DOM и жизненный цикл окна
Название проекта, имя файла и ответ API считайте данными. Передавайте их через titleText и text. Параметр html предназначен для разметки и не обещает очистку произвольного ввода. Конкатенация пользовательской строки с тегами создаёт ненужный риск и ломает отображение символов.
Когда нужен сложный блок, создайте DOM самостоятельно. Например, две строки описания можно собрать без HTML-интерполяции. Тогда название с угловыми скобками останется текстом:
function showProjectInfo(project) {
const content = document.createElement('div');
const name = document.createElement('p');
const status = document.createElement('p');
name.textContent = project.name;
status.textContent = `Статус: ${project.status}`;
content.append(name, status);
return Swal.fire({ titleText: 'Карточка проекта', html: content });
}
Обработчики, которые относятся только к созданным узлам окна, удобно подключать после открытия. Если добавляется интервал, слушатель документа или другой внешний ресурс, его надо освободить. Для надёжной очистки используйте didDestroy: он учитывает и ситуацию, когда окно заменили следующим. Одного didClose для этого недостаточно.
async function showTimedNotice() {
let interval;
const remaining = document.createElement('p');
remaining.textContent = 'Сообщение закроется через 5 секунд.';
return Swal.fire({
titleText: 'Сообщение закроется автоматически',
html: remaining,
timer: 5000,
timerProgressBar: true,
confirmButtonText: 'Понятно',
didOpen: () => {
interval = setInterval(() => {
const left = Swal.getTimerLeft();
if (left !== undefined) {
remaining.textContent = `Осталось: ${Math.ceil(left / 1000)} с`;
}
}, 250);
},
didDestroy: () => clearInterval(interval)
});
}
Не создавайте ещё один Swal.fire внутри незавершённого preConfirm только ради ошибки: текущий диалог может быть уничтожен вместе с вводом. Покажите сообщение в нём, верните false и дайте человеку продолжить. Отдельный экран успеха открывайте после завершения первого Promise.
Источник: обработка preConfirm и ошибок подтверждения.
Мобильный экран и клавиатура: окно должно помогать
На телефоне особенно заметны длинные подписи, низкие кнопки и поле, которое скрывает клавиатура. Ограничьте ширину окном браузера, разрешите кнопкам переноситься и оставьте читаемый размер ввода. Проверяйте не только первый экран, но и ошибку, открытое меню выбора, длинное название и появление экранной клавиатуры.
/* Класс добавляется через customClass: { popup: 'service-dialog' }. */
.service-dialog { width: min(32rem, calc(100vw - 2rem)); }
.service-dialog .swal2-actions { flex-wrap: wrap; gap: .5rem; }
.service-dialog button { min-height: 44px; }
.service-dialog input,
.service-dialog select,
.service-dialog textarea { font-size: 16px; }
@media (prefers-reduced-motion: reduce) {
.service-dialog { animation: none !important; }
}
Путь с клавиатуры проверяется целиком: открыть, прочитать заголовок и подпись, перейти между полем и кнопками, исправить ошибку, отменить и продолжить работу. Для опасного действия первоначальный фокус разумно поставить на отмену. После закрытия он возвращается к инициатору; если строка с кнопкой удалена, выберите следующий логичный элемент вручную.
Не отключайте Escape и закрытие без объяснения. Во время короткого запроса допустимо защитить незавершённый шаг, но для долгой операции нужен понятный выход. Toast не подходит для обязательного решения: человек может не заметить его вовремя. Автоматическое закрытие не должно лишать доступа к важному результату.
Избегайте нескольких модальных слоёв. При совместном использовании с Bootstrap modal проверьте обработку Escape и фокус: для перехвата клавиши у SweetAlert2 есть keydownListenerCapture, но сам по себе этот параметр не исправляет запутанный сценарий. В демонстрациях статьи диалоги доступны в области экрана; при переносе в чужой iframe это ограничение нужно проверить отдельно.
На узком экране проверьте длинную кнопку отмены и сообщение об ошибке в несколько строк. На настольном — масштаб страницы 200% и последовательность Tab/Shift+Tab. Не полагайтесь только на цвет: опасное действие должно быть понятно из текста, а успех — из фактического изменения карточки. Настройка ARIA у библиотеки помогает, но качество подписей и доступность собственных полей остаются ответственностью автора интерфейса.
Частые ошибки и как проверить готовый сценарий
| Симптом | Почему возникает | Что изменить |
|---|---|---|
| Действие выполняется сразу | Promise принят за ответ пользователя | Дождаться await и проверить isConfirmed |
| Отмена считается подтверждением | Проверяется весь объект result | Разделить Confirm, Deny и Dismiss |
| Ошибка закрывает окно | preConfirm вернул объект ошибки | Показать сообщение и вернуть false |
| Успех появился при HTTP 500 | Не проверен response.ok | Проверить статус и формат ответа |
| Вместо названия появилась разметка | Данные подставлены в html | Использовать text/titleText или textContent |
| Повторно открылись два шага | Инициатор не защищён от повторов | Busy-флаг и очистка в finally |
| Toast исчезает во время чтения | Пауза учитывает только мышь | Учитывать фокус и сохранить важное действие на странице |
| После закрытия потерялся фокус | Исходная кнопка удалена или отключена | Вернуть фокус к доступному элементу после обновления DOM |
Проверяйте отмену на каждом шаге, а не только успешный путь. Для ввода нужны пустая строка, пробелы, максимальная длина и необычные символы. Для запроса — медленный ответ, ошибка HTTP, некорректный JSON и повтор. Для удаления — нулевой выбор, изменение списка и восстановление. Эти проверки объясняют поведение гораздо лучше, чем десяток вариантов цвета одной кнопки.
Не переносите старые настройки без сверки с версией. Например, allowEnterKey в используемой ветке помечен устаревшим. У modal и toast разные роли и совместимые параметры: общую конфигурацию нужно собирать с учётом режима, а не передавать всё сразу.
Источник: параметры, устаревшие настройки и ограничения toast.
Исходники и следующий шаг в своём проекте
Выберите одно действие, которое уже есть в вашем сервисе: изменение названия, подтверждение удаления или повтор запроса. Сначала определите данные до открытия и результат после закрытия. Затем настройте окно, свяжите решение с моделью и проверьте отмену. Так библиотека становится частью понятного рабочего процесса.
Скачать мастерскую и все шесть интерфейсов. Сохранённый самостоятельный HTML позволяет продолжить эксперименты вне статьи. Общую страницу из исходников запускайте по инструкции README; отдельные автономные примеры открываются без внешних зависимостей.
- Официальная документация SweetAlert2 — примеры, параметры и методы.
- Релиз 11.26.25 — закреплённая версия демонстраций.
- Описания API и типов результата — детали полей, preConfirm и жизненного цикла.
- Лицензия MIT — условия использования и сохранения уведомления об авторских правах.
Диалоги хорошо дополняют рабочие таблицы Tabulator: выбранные строки дают контекст подтверждению, а ответ сервера обновляет таблицу. Для состояния небольшого интерфейса пригодится Alpine.js. В обоих случаях окно отвечает за короткое взаимодействие, а данные и правила остаются явными частями приложения.


