Liberman GonzalezProduct designer · Design engineer
ES / EN
← Todos los estudios

Confirmar según el riesgo

Un botón destructivo que pide más cuanto más grave es la acción: lo reversible se deshace, lo medio se confirma con un segundo clic y lo irreversible, escribiendo el nombre.

El plus
La fricción crece con la consecuencia, y nadie espera ni cuenta segundos.
Lente
Proporción
Stack
React · CSS · Web Animations API

El patrón

Borrar, cancelar o descartar casi siempre pasa por el mismo modal: "¿Estás seguro?", con dos botones. Funciona la primera vez. A la décima, la mano hace clic en "Sí" antes de leer, y el modal deja de proteger.

Otra variante pide mantener el botón presionado hasta que se llena. Protege mejor, pero hace esperar con el dedo apretado incluso cuando la acción no lo amerita.

El plus

No todas las acciones destructivas pesan lo mismo, así que el botón no pide lo mismo para todas. Hay tres niveles: , y .

El costo extra nunca es tiempo: es un clic, y escribir solo cuando de verdad importa.

Lo reversible se deshace

Descartar un borrador se rehace en un minuto. Pedir confirmación ahí es ruido: el botón ejecuta al primer clic y ofrece Deshacer durante 5 segundos.

Un relleno azul claro cruza el botón mientras se puede deshacer. Cuando llega al final, la acción queda hecha.

Dos clics, no un doble clic

Cancelar algo que deja registro merece un segundo de atención. El primer clic arma el botón: dice "Confirmar eliminación" y un relleno rojo claro empieza a cruzarlo. Si haces el segundo clic antes de que llegue al final, se confirma; si no, se desarma solo. Clic fuera, Esc o perder el foco también lo desarman.

El riesgo de este patrón es el doble clic: dos clics rápidos lo confirmarían sin que nadie leyera nada. Por eso el segundo clic solo cuenta si llega después de 400 ms.

const confirm = () => {
  // Un segundo clic más rápido que esto es un doble clic, no una decisión.
  if (performance.now() - armedAt.current < 400) return;
  setPhase('confirming');
};

Pruébalo: haz doble clic sobre el botón. Se arma, pero no se confirma.

El texto cambia donde pasa el relleno

El texto empieza gris y se vuelve rojo justo en el borde del relleno, letra por letra. No se calcula nada por letra: el botón tiene tres capas con el mismo contenido, y cada una se recorta con clip-path.

  1. Base: gris, la única que lee el lector de pantalla.
  2. Relleno: rojo claro con el texto en rojo. Se descubre de izquierda a derecha.
  3. Barrido: rojo con el texto claro. Aparece al confirmar.
fill.animate(
  [{ clipPath: 'inset(0 100% 0 0)' }, { clipPath: 'inset(0 0% 0 0)' }],
  { duration: 4000, easing: 'linear', fill: 'forwards' },
);

Encima va un borde interno de 1,5 px en su propia capa: en gris es un 6 % de negro, en rojo un anillo blanco. Si fuera un borde del botón, el relleno lo taparía.

Lo irreversible se escribe

Borrar algo sin vuelta atrás pide algo que no se hace por inercia: escribir el nombre. El botón abre un panel en la misma página, sin modal, y el botón rojo solo se activa cuando el nombre coincide.

La ayuda dice cuántas letras faltan, y mayúsculas y tildes no cuentan: la fricción está en escribirlo, no en adivinar cómo. Pegar también está permitido; bloquearlo castiga a quien usa un gestor de contraseñas o un lector de pantalla.

Estados

EstadoQué cambia
ReposoGris, con sombra y borde interno. La pista "2 clics" dice el costo antes de tocar.
HoverFondo rojo suave: anuncia que es destructivo, no ejecuta nada.
FocoAnillo azul de 2 px, distinto del rojo de peligro.
ArmadoEl relleno avanza en 4 s y el texto pasa de gris a rojo a su paso.
ConfirmandoEl rojo barre el botón de izquierda a derecha.
CargandoTodo el botón en rojo, con anillo blanco: "Eliminando…".
ÉxitoCheck y "Archivo eliminado".
ErrorÍcono y texto, no solo color. Un clic vuelve a armarlo.
Movimiento reducidoSin barridos: el estado cambia de golpe y la pista cuenta los segundos.

Cada cambio se anuncia en una región role="status", y la acción ocurre al soltar el clic, nunca al presionar. Activa "Sin movimiento" en el escenario para ver la versión sin animación.

Tokens

Sin colores sueltos. En reposo: --color-bg-control, --color-text-control, --color-line-edge y --color-shadow-control. En el relleno: --color-bg-danger-progress y --color-text-danger. En el barrido: --color-bg-danger, --color-text-on-danger y --color-line-on-danger. Todos tienen valor en claro y en oscuro, y las duraciones salen de --duration-fast y --duration-base, que valen 0 con movimiento reducido.