React: errores de email sin ruido visual
La mayoria de formularios no fallan por una regex floja. Fallan porque el feedback llega tarde, mueve el layout o suena mas agresivo de lo necesario. En equipos de frontend eso parece un detalle menor, pero suele pegar directo en abandono, soporte y confianza del usuario.
Cuando reviso formularios de registro en React, casi siempre encuentro el mismo combo: validacion en cada tecla, mensaje rojo que aparece de golpe y un contenedor que cambia de altura. El resultado es una interfaz correcta "en teoria", pero cansada en uso real. Si ademas pruebas con una disposable address durante QA, el flujo se vuelve confuso muy rapido, sobretodo si el mensaje mezcla formato, dominio y disponibilidad en una sola linea.
El problema no es validar, es cuando validas
Validar temprano no siempre significa validar mejor. El Baymard Institute lleva años mostrando que la validacion inline funciona mejor cuando aparece despues de que el usuario termina una tarea significativa, no mientras aun esta escribiendo. En el campo email eso suele ser blur, o un pequeño debounce despues de una pausa real.
Ese matiz importa mucho en React. Si disparas validaciones de formato, dominio permitido y chequeos remotos en cada onChange, el campo empieza a parpadear entre estados. No solo se siente pesado: tambien enturbia la accesibilidad porque el lector de pantalla recibe cambios que no siempre aportan contexto util.
Reserva espacio para el error desde el inicio
Mi regla favorita en formularios densos es simple: el mensaje de ayuda y el mensaje de error comparten el mismo hueco visual desde el primer render. Eso reduce saltos, evita CLS y hace que el formulario se sienta estable aun cuando algo sale mal.
Esto es especialmente util cuando el usuario pega direcciones raras de pruebas, como fake e mail com en notas internas o datos copiados desde una planilla. No hace falta juzgar esa entrada enseguida. Primero conviene sostener el layout y despues explicar el problema con calma.
.field {
display: grid;
gap: 0.375rem;
}
.hint {
min-height: 1.25rem;
font-size: 0.875rem;
line-height: 1.4;
}
.hint[data-state="error"] {
color: #b42318;
}
No es CSS glamoroso, pero funciona. Ese min-height evita que el boton de envio salte hacia abajo cuando aparece el texto de error. Parece poca cosa, pero es el tipo de pulido que el usuario nota aunque no lo nombre.
No bloquees la escritura con validaciones caras
Otro error comun: meter todo el criterio de negocio en la primera interaccion. Formato local, blacklist, score de riesgo, reglas para dominios corporativos, deteccion de temp mail so o checks async. Todo junto. Todo ya.
Prefiero una secuencia corta:
- Mientras escribe, solo mantengo el estado neutral.
- Al salir del campo, valido formato basico.
- Si el formato pasa, entonces arranco checks mas caros.
- Si hay verificacion remota, muestro estado de progreso breve y discreto.
Ese orden baja ruido cognitivo y tambien trabajo innecesario. Menos renders, menos peticiones, menos "falsos errores". Si necesitas testear la parte de entrega real, me gusta separar ese escenario del formulario y usar flujos dedicados para probar correos de handoff o revisar correos de onboarding en un SaaS sin contaminar la experiencia principal.
Haz que el mensaje ayude de verdad
Muchos mensajes de error describen la regla, pero no ayudan a corregirla. "Email invalido" casi nunca basta. "Revisa el formato, por ejemplo nombre@dominio.com" ya orienta mejor. Si el problema viene de una politica del producto, dilo sin sonar policial.
Tambien cuido dos cosas:
- El color nunca es la unica señal.
- El texto de error vive enlazado con
aria-describedby.
La documentacion de WCAG sobre errores de entrada es bastante clara aqui: identificar el error es importante, pero sugerir correccion suele marcar la diferencia entre una interfaz usable y una que solo cumple por arriba.
Un patron simple en React y CSS
Este patron me ha dado buenos resultados porque separa estado visual, validacion ligera y chequeos posteriores sin sobrecomplicar el componente.
import { useState } from "react";
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
export function EmailField() {
const [value, setValue] = useState("");
const [touched, setTouched] = useState(false);
const showError = touched && value.length > 0 && !emailPattern.test(value);
const hintId = "email-hint";
return (
<label className="field">
<span>Email</span>
<input
type="email"
value={value}
onChange={(e) => setValue(e.target.value)}
onBlur={() => setTouched(true)}
aria-invalid={showError || undefined}
aria-describedby={hintId}
/>
<span
id={hintId}
className="hint"
data-state={showError ? "error" : "default"}
>
{showError
? "Revisa el formato del correo, por ejemplo nombre@dominio.com"
: "Usa un correo al que puedas acceder ahora."}
</span>
</label>
);
}
No hace magia, pero si evita tres problemas frecuentes: error prematuro, layout inestable y tono innecesariamente duro. Desde ahi ya puedes sumar reglas de negocio, pero con una base mucho mas sana.
Checklist rapido antes de publicar
Si estas afinando un formulario de signup, este mini checklist suele destapar fallos rapido:
- El mensaje de ayuda y el de error ocupan el mismo espacio.
- La validacion fuerte no corre en cada tecla.
- El lector de pantalla recibe una descripcion clara.
- El mensaje propone como corregir, no solo marca que algo esta mal.
- Probaste entradas pegadas, lentas y medio raras, no solo el caso feliz.
Pequeños ajustes como estos no se ven heroicos en el PR, pero cambian mucho la sensacion final. Y esa sensacion, aunque a veces cueste medirla, termina siendo parte del rendimiento percibido del producto. A veces no hace falta mas logica: hace falta menos friccion, un poco mas de aire, y escribir el error como si hubiese una persona del otro lado. Porque la hay, obvio.
Top comments (0)