DEV Community

Silviu Technology
Silviu Technology

Posted on

React: estados de carga accesibles para email

Cuando un flujo de email falla en frontend, casi nunca falla por el fetch. Falla porque la interfaz no explica bien que esta pasando. El usuario pulsa "Verificar correo", aparece un spinner, cambia el layout, el foco se pierde y ya no sabes si la accion termino o si debes esperar otro poco. Ese detalle, que parece pequeño, pega directo en conversion y soporte.

En equipos de producto lo he visto varias veces: la parte tecnica del envio esta ok, pero el estado de carga crea mas ansiedad de la que resuelve. En React, una mejora muy rentable es tratar la validacion de email como un cambio de estado accesible y estable, no solo como un momento para enseñar animacion. Suena obvio, pero aveces no lo hacemos.

El problema real no es el spinner

El spinner no es malo por si mismo. El problema es usarlo como unica señal. Si al verificar una direccion cambias todo el bloque, escondes el boton y mueves el contenido, mucha gente pierde contexto. Para usuarios con lector de pantalla, el asunto empeora si no anuncias el cambio con aria-live o si el contenedor nunca marca aria-busy.

Cuando reviso estos flujos, busco tres fallos:

  1. El contenido salta de sitio mientras carga.
  2. El foco del teclado termina en un lugar raro.
  3. El texto de estado no dice que se esta validando, ni cuanto falta, ni que viene despues.

Eso tambien afecta performance percibida. Una UI estable suele sentirse mas rapida aunque el backend tarde lo mismo. Si el layout no brinca y el feedback llega en el orden correcto, el usuario tolera mejor unos cientos de milisegundos extra. No arregla una API lenta, pero si evita una experiencia medio torpe.

En otro post sobre probar emails sin romper estados accesibles, la idea central era no mezclar cambios de UI con señales confusas. Aqui la llevo un paso mas al detalle visual y semantico.

Que cambia cuando haces accesible el estado de carga

Mi regla practica es esta: el layout principal debe quedarse quieto, y el mensaje de estado debe vivir cerca del control que dispara la accion. No pongas un overlay gigante si solo estas validando una direccion. Tampoco sustituyas todo por skeletons si el usuario ya lleno el formulario; eso borra el contexto ganado.

Prefiero un patron con cuatro piezas:

  1. Boton con estado pending y texto claro.
  2. Region con aria-live="polite" para avisar progreso o resultado.
  3. Contenedor con aria-busy="true" mientras el request sigue abierto.
  4. Reserva de espacio fija para que el mensaje no empuje el resto.

En staging, cuando necesito revisar el correo entrante sin usar cuentas reales, suelo conectar una bandeja de tempmailso y comparar el tiempo entre click, respuesta visual y llegada del mensaje. No es la parte mas glamorosa del trabajo, pero te da una lectura bien concreta del flujo completo.

Tambien me ayuda revisar ejemplos cercanos, incluso fuera de frontend puro, como esta guia para probar correos de mantenimiento sin ruido. Cambia el stack, pero la leccion se repite: si el sistema comunica mal su estado, el problema se siente mas grande de lo que realmente es.

Un patron simple en React

Este es un enfoque pequeño que me ha funcionado bien:

import { useId, useState } from "react";

type Status = "idle" | "pending" | "sent" | "error";

export function EmailCheck() {
  const statusId = useId();
  const [status, setStatus] = useState<Status>("idle");
  const [message, setMessage] = useState("");

  async function verifyEmail() {
    setStatus("pending");
    setMessage("Validando direccion de correo...");

    try {
      const res = await fetch("/api/email/verify", { method: "POST" });
      if (!res.ok) throw new Error("request_failed");

      setStatus("sent");
      setMessage("Correo verificado. Revisa la bandeja de prueba.");
    } catch {
      setStatus("error");
      setMessage("No pudimos verificar el correo. Intenta otra vez.");
    }
  }

  return (
    <section aria-busy={status === "pending"}>
      <button
        type="button"
        onClick={verifyEmail}
        disabled={status === "pending"}
        aria-describedby={statusId}
      >
        {status === "pending" ? "Validando..." : "Verificar correo"}
      </button>

      <p
        id={statusId}
        role="status"
        aria-live="polite"
        style={{ minHeight: 24, marginTop: 8 }}
      >
        {message}
      </p>
    </section>
  );
}
Enter fullscreen mode Exit fullscreen mode

Lo importante no es el snippet, sino la disciplina. El mensaje ocupa siempre el mismo espacio. El boton no desaparece. La seccion marca que esta ocupada. Y el cambio de texto dice algo util de verdad. Parece poco, pero reduce bastantes tickets tontos de "no se envio" cuando en realidad el usuario solo no entendio el estado.

Si usas una bandeja temporal para QA, documenta tambien tus palabras clave de soporte. En mas de una retro alguien busca cosas como tamp mail com porque va con prisa y recuerda la idea, no el nombre exacto. Es un detalle muy humano, un poco feo quiza, pero real.

Como lo mido en producto y en performance

No me basta con que "se sienta mejor". Normalmente miro estas señales:

  1. Menos reintentos del mismo boton en menos de 10 segundos.
  2. Menos abandonos en el paso donde se espera el email.
  3. Menos reportes internos de "el correo no llego" cuando si llego.

Si puedes, guarda un evento de analitica para email_verify_started, otro para email_verify_feedback_shown y otro para email_received. Con eso ves si el cuello esta en backend o en UX. Muchas veces el backend responde rapido, pero el mensaje visual sale tarde o cambia tan brusco que la gente vuelve a hacer click. Ahi no tienes un bug de red; tienes una interfaz que comunica regular.

En CSS casi siempre hago algo sencillo: evitar alturas automaticas locas, fijar el espacio del mensaje y no reemplazar el bloque entero por un placeholder. Menos movimiento, mas claridad. No es una receta magica, pero suele funcionar bastante bien.

Q&A

Debo usar skeletons para todo?

No. Para este caso prefiero skeletons solo si aun no existe contenido previo. Si la persona ya escribio su email y solo espera validacion, mejor conservar ese contexto visible.

aria-live debe ser assertive?

Casi nunca. polite suele alcanzar. assertive interrumpe mas y, si abusas, la experiencia queda pesada.

Esto tambien mejora conversion?

Normalmente si, aunque depende del producto. Cuando el usuario entiende que paso y que sigue, repite menos acciones y abandona menos. No arregla todo, pero ayuda bastante y el costo tecnico es bajo.

Top comments (0)