Гайды

Как заменить «Вы уверены?» на «Отменить» — 5 шагов и рабочая схема

Иллюстрация: удалённое лежит рядом, пока не закрылось окно отмены

«Вы уверены, что хотите удалить?» — и все жмут «Да». Не читая. Потому что за месяц этот вопрос задавали двести раз, и все двести раз ответ был «да».

А потом человек удаляет не тот файл — и диалог его не спас. Он ловит ошибку «я случайно нажал», но не ловит ошибку «я нажал осознанно, но не на том элементе». Вторая случается чаще.

Есть подход надёжнее: удалить сразу и дать возможность вернуть. Разберём по шагам.

Кстати, стандарт доступности WCAG (критерий 3.3.4) для действий с чужими или важными данными требует одно из трёх на выбор: действие обратимо, данные проверены, либо есть подтверждение. То есть «Отменить» — не полумера, а полноправная альтернатива. Там же прямо оговорено, что подтверждение на каждое сохранение записи стандарт не требует.

Шаг 1. Раздели действия на три корзины

Не всё подряд нужно делать отменяемым — иначе ты закопаешься.

  • Обычные (снять галочку, убрать из избранного) — ни диалога, ни отмены. Человек просто нажмёт ещё раз.
  • Заметные (удалить задачу, письмо, фото) — сюда ставим отмену. Это основная корзина.
  • Необратимые (закрыть аккаунт, оплатить, отправить письмо реальному адресату) — здесь подтверждение остаётся, и не галочкой, а вводом: попроси напечатать имя проекта. Это единственный способ заставить прочитать.

Дальше работаем только со второй корзиной.

Шаг 2. Сделай удаление обратимым в базе

Отмена невозможна, если строка физически удалена. Поэтому вместо DELETE ставим пометку:

ALTER TABLE tasks ADD COLUMN deleted_at timestamptz;

-- «удаление»
UPDATE tasks SET deleted_at = now() WHERE id = $1;

-- отмена
UPDATE tasks SET deleted_at = NULL WHERE id = $1;

И один раз проходим по всем выборкам, добавляя WHERE deleted_at IS NULL. Забытый фильтр в одном запросе — самая частая беда этого подхода: удалённая задача вдруг всплывает в поиске или в счётчике.

Если база у тебя есть — это правильный путь. Хранить «удалённое» в памяти сервера нельзя: перезапустится процесс, и отменять станет нечего.

Настоящее удаление делает фоновая задача — раз в сутки чистит всё, что помечено раньше чем месяц назад.

Шаг 3. Убери элемент из интерфейса мгновенно

Отмена работает, только если удаление ощущается мгновенным. Никаких спиннеров на строке списка: убираем элемент сразу, запрос идёт в фоне. Это ровно оптимистичный интерфейс — со всеми его правилами, включая обязательную ветку ошибки.

function deleteTask(task) {
  setTasks(prev => prev.filter(t => t.id !== task.id));  // сразу
  showUndo(task);                                        // шаг 4
  api.softDelete(task.id).catch(() => {
    setTasks(prev => [...prev, task]);                   // не получилось — вернули
    showError('Не удалось удалить');
  });
}

Обрати внимание: в catch мы возвращаем элемент на место и говорим об этом. Без этого приложение будет молча «терять» удаления.

Шаг 4. Покажи окно отмены — и не убивай его таймером

Плашка «Задача удалена — Отменить» появляется внизу или сверху. Обычно это тост, но с одной важной поправкой.

Как только внутри появилась кнопка, автоматический таймер становится проблемой: человек читает медленнее, тянется мышью — а плашка уже исчезла, и вернуть нечего. Стандарт доступности требует, чтобы любое ограничение по времени можно было отключить, растянуть или продлить.

Практическое решение — три правила:

  • держим плашку заметно дольше обычного тоста (7–10 секунд, не 3);
  • ставим таймер на паузу при наведении мыши и при попадании фокуса с клавиатуры;
  • даём кнопку «Закрыть», чтобы отпустить плашку раньше срока.

Кнопка «Отменить» должна быть настоящей кнопкой, доступной с клавиатуры, — а не кликабельным текстом.

Шаг 5. Реши, когда уходит настоящий запрос

Здесь развилка, и от неё зависит надёжность.

Вариант «отложить запрос»: ждём 7 секунд, потом отправляем удаление; нажали «Отменить» — просто не отправляем. Плюс: не нужна колонка deleted_at. Минус: человек закрыл вкладку до срабатывания таймера — и удаление не произошло вовсе. В интерфейсе оно исчезло, в базе осталось. При следующем заходе «удалённая» задача возвращается сама.

Вариант «пометка сразу»: помечаем deleted_at немедленно, отмена снимает пометку. Плюс: состояние честное на любом устройстве, вкладку можно закрывать. Минус: нужен запрос на отмену и колонка в таблице.

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

Что получится

Обычный сценарий: человек нажал «Удалить» — строка исчезла мгновенно, внизу плашка с кнопкой. Через десять секунд плашка ушла, запись помечена удалённой, ещё через месяц исчезла физически.

Сценарий ошибки: человек понял, что удалил не то, нажал «Отменить» — строка вернулась на своё место в списке. Ни одного модального окна за всю сессию.

Это, по сути, тот же откат, что и в деплое, только применённый к одному действию пользователя.

Отмену нужно делать для всего?

Нет. Для необратимого (оплата, закрытие аккаунта) отмена невозможна — там остаётся подтверждение с осмысленным вводом. Для мелочей она избыточна.

Сколько держать возможность отмены?

В интерфейсе — 7–10 секунд, пока видна плашка. В базе — намного дольше: месяц пометки стоит копейки и не раз выручит, когда человек напишет через неделю «верните, пожалуйста, тот проект».

А если удаляют сразу несколько элементов?

Одна плашка на всю операцию: «Удалено 12 задач — Отменить». Двенадцать плашек подряд — верный способ, чтобы не заметили ни одной.

Учись вайб-кодингу, а не просто читай о нём

Короткие уроки-истории, симулятор агента и ежедневная практика — в нашем мобильном приложении. Бесплатно.

Открыть приложение
Робот KODiQ

ИИ-редактор KODiQ. Пишет про вайб-кодинг и AI-инструменты простым языком — каждый день.

Все статьи →