Hay formularios que fallan no por la validacion, sino por la manera en que muestran la validacion. El email se comprueba bien, el backend responde rapido, pero el texto aparece tarde, empuja el boton y hace que todo el bloque cambie de sitio. Ese micro salto parece pequeño; en uso real molesta bastante y transmite una sensacion rara, como si el producto no estuviera del todo fino.
En trabajo de frontend lo trato como un problema de UX y de Performance al mismo tiempo. Google sigue recomendando reducir el Cumulative Layout Shift porque los cambios inesperados afectan la experiencia y pueden provocar interacciones fallidas. En formularios eso se nota mucho mas de lo que el equipo suele pensar.
Por que un mensaje de email puede romper una buena UI
Un campo de email casi siempre acumula muchos estados: vacio, invalido, comprobando, permitido, sospechoso, ya usado, o pendiente de confirmacion. Si cada estado aparece creando y destruyendo bloques de texto, el formulario empieza a respirar mal. No hay un bug grande, pero si una suma de roces. Y esos roces bajan confianza, que al final tambien pega en conversion.
Me pasa seguido en productos donde el copy de ayuda se deja para el final. Primero resolvemos fetch, debounce y cancelacion. Luego alguien añade un mensaje de dos lineas y, sin querer, mueve el CTA justo cuando la persona iba a tocarlo. Se puede medir como CLS, sí, pero tambien se percibe como "esto esta medio inestable".
Otra cosa curiosa es que el problema empeora cuando el flujo tiene dependencias de negocio. Si el equipo necesita registrar intentos de email sin perder contexto, el frontend deberia mostrar esos estados con calma y continuidad. Si la interfaz se agita demasiado, la señal correcta se vuelve confusa.
El patron que mejor me funciona en React
Mi regla base es simple: el area de ayuda del email debe existir desde el primer render. No espero a que ocurra un error para montarla. Reservo altura, mantengo el aria-live en un nodo estable y solo cambio contenido y tono visual. Parece un detalle tonto, pero da bastante orden.
import { useEffect, useState } from "react";
type EmailHint =
| { kind: "idle"; message: string }
| { kind: "checking"; message: string }
| { kind: "ok"; message: string }
| { kind: "warning"; message: string };
export function SignupEmailField() {
const [email, setEmail] = useState("");
const [hint, setHint] = useState<EmailHint>({
kind: "idle",
message: "Usa un correo al que puedas entrar hoy.",
});
useEffect(() => {
if (!email.includes("@")) {
setHint({
kind: "idle",
message: "Usa un correo al que puedas entrar hoy.",
});
return;
}
const controller = new AbortController();
setHint({ kind: "checking", message: "Comprobando el correo..." });
const timer = setTimeout(async () => {
const response = await fetch(`/api/email-check?value=${encodeURIComponent(email)}`, {
signal: controller.signal,
});
const result = await response.json();
if (result.ok) {
setHint({ kind: "ok", message: "Correo listo para continuar." });
return;
}
setHint({ kind: "warning", message: result.message });
}, 250);
return () => {
controller.abort();
clearTimeout(timer);
};
}, [email]);
return (
<label className="field">
<span>Correo</span>
<input
type="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
aria-describedby="email-hint"
/>
<span id="email-hint" className={`hint hint--${hint.kind}`} aria-live="polite">
{hint.message}
</span>
</label>
);
}
El punto importante no es el hook. Es la estabilidad del bloque que comunica el estado. Cuando ese nodo ya existe, el lector de pantalla recibe cambios mas limpios y la pantalla evita empujones algo torpes. No siempre queda perfecto a la primera, pero casi siempre mejora.
CSS pequeño, impacto bastante grande
Con muy poco CSS ya se gana bastante:
.hint {
display: block;
min-height: 2.75rem;
margin-top: 0.5rem;
line-height: 1.4;
font-size: 0.92rem;
}
.hint--idle {
color: #60707d;
}
.hint--checking,
.hint--ok {
color: #1f6a46;
}
.hint--warning {
color: #a33a12;
}
La clave es min-height. Ese hueco fijo le quita nervios a la interfaz. En mobile prefiero gastar un poco de alto antes que aceptar un salto cuando la respuesta tarda 300 o 500 ms. No es una solución glamourosa, pero funciona muy bien y es barata de mantener.
Tambien ayuda no mezclar animaciones decorativas con mensajes criticos. Si el estado del correo cambia, yo intento que el color y el texto hagan el trabajo. Transiciones largas, fades raros o iconos bailando suelen meter ruido. A veces quedan bonitos en Figma, pero en producto real cansan mas de lo esperado, o eso vi varias veces.
Donde encaja la deteccion de correo temporal desechable
No todos los productos deben bloquear un correo temporal desechable. Muchos solo quieren marcar riesgo, distinguir trafico de QA o adaptar el onboarding. Por eso me gusta que la comprobacion devuelva contexto y no una sancion ciega. Si el sistema detecta un patron tipo temp gamil com o un dominio desechable conocido, prefiero un mensaje que explique la consecuencia inmediata.
Por ejemplo: "Este correo puede expirar antes de que llegue el enlace de activacion". Eso es mas util que un "correo invalido" generico. Y si el equipo tambien necesita probar correos de renovacion en SaaS, conviene separar claramente los mensajes de prueba de los mensajes de riesgo para personas reales.
Mi criterio es este:
- bloquear solo cuando hay una politica clara
- avisar cuando el riesgo es contextual
- no mover el layout para comunicar nada de eso
- dejar el siguiente paso bien clarito
La parte de Accesibilidad entra fuerte aqui. Si el copy se vuelve largo o tecnico, el usuario tarda mas en entenderlo. Un mensaje corto, estable y bien asociado al campo suele rendir mejor. No hace falta ponerse ultra creativo; hace falta ser preciso.
Checklist antes de publicar el formulario
Antes de dar por bueno un formulario con validacion de email, reviso estas cosas:
- el mensaje de ayuda existe desde el primer render
- el boton principal no cambia de posicion
- los requests viejos se abortan cuando la persona sigue escribiendo
- el estado
checkingno bloquea de mas - el mensaje explica accion y efecto, no solo error
- el area de ayuda sigue legible con zoom y en mobile
Es una lista corta, pero evita bastantes regresiones feas. Muchas pantallas estan tecnicamente bien y aun asi se sienten frágiles. Cuando eso pasa, casi siempre hay una capa de feedback visual resuelta con prisa.
Q&A
¿Reservar altura no desperdicia espacio?
Un poco, sí. Pero normalmente el coste visual es menor que el coste de mover el contenido en pleno uso. En formularios cortos compensa casi siempre.
¿Conviene ocultar el mensaje cuando el email ya es valido?
Yo no lo haría. Prefiero mantener el bloque y cambiar a una confirmacion breve. Si lo escondes del todo, el layout vuelve a tener una pequeña sacudida, y no vale la pena.
¿Esto solo aplica a React?
No. Aplica a cualquier UI con validaciones asincronas. React solo hace que el problema aparezca mas seguido porque los rerenders lo vuelven muy visible.
Top comments (2)
si el mensaje de error sigue visible es raro a nivel UX. El usuario va a seguir pensando que su input es incorrecto todavía porque el estado no cambió. Se podría colocar un borde verde, un icono de tilde, un mensaje escondido para usuarios de lectores de pantalla, o un mensaje visible avisando que el input se ha completado correctamente.
te recomendaría fehacientemente realizar los componentes y luego testearlos con los lectores de pantalla y teclado para tener una noción real de cómo se interactúa con ellos en vez de copiar y pegar texto generado por claude