Tema
Validación de formularios
Los mensajes por defecto del navegador están en el idioma del sistema, aparecen en globos difíciles de estilizar y no se pueden personalizar bien. Este snippet aprovecha la validación nativa de HTML5 (required, type="email", minlength, pattern) pero muestra mensajes propios en español, debajo de cada campo, accesibles para lectores de pantalla y con reglas personalizadas.
Demo
Cómo funciona
- El formulario lleva
novalidatepara desactivar los globos del navegador. - Cada campo declara sus reglas con atributos HTML normales.
mensajeDe()leeinput.validity(la API de validación del navegador) y traduce cada problema a un mensaje claro.- Reglas extra con atributos
data-:data-igual-a="password": debe coincidir con otro campo.data-validar="passwordFuerte": ejecuta una función dereglas.data-mensaje-pattern="...": mensaje para cuando fallapattern.
- Se valida al salir del campo (
blur) y, una vez tocado, mientras escribe. Así no regañas al usuario antes de tiempo. - Al enviar se validan todos, se enfoca el primer campo con error y se bloquea el botón mientras se envía.
HTML de un campo
Cada campo sigue esta estructura; el id del error es el id del input más -error:
html
<div class="campo">
<label for="telefono">Teléfono (10 dígitos)</label>
<input id="telefono" name="telefono" type="tel" pattern="\d{10}" inputmode="numeric"
data-mensaje-pattern="Escribe 10 dígitos, sin espacios">
<span class="error" id="telefono-error" aria-live="polite"></span>
</div>Atributos de validación disponibles
| Atributo | Ejemplo | Valida |
|---|---|---|
required | required | Que no esté vacío |
type | email, url, number | Formato |
minlength / maxlength | minlength="8" | Longitud del texto |
min / max | min="1" max="10" | Rango numérico o de fecha |
pattern | pattern="[A-Z]{4}\d{6}" | Expresión regular |
step | step="0.01" | Incrementos |
Enviar al servidor con fetch
Sustituye la función que se pasa a activarValidacion por una petición real y muestra los errores que devuelva el backend (por ejemplo, "correo ya registrado"):
js
activarValidacion(document.getElementById('registro'), async (datos) => {
const res = await fetch('/api/registro', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(datos),
});
const json = await res.json();
if (res.status === 422 || res.status === 409) {
// El servidor indica errores por campo: [{ campo, mensaje }]
(json.detalles ?? [{ campo: 'email', mensaje: json.error }]).forEach(({ campo, mensaje }) => {
const input = document.querySelector(`[name="${campo}"]`);
if (!input) return;
document.getElementById(input.id + '-error').textContent = mensaje;
input.closest('.campo').classList.add('invalido');
input.setAttribute('aria-invalid', 'true');
});
return;
}
if (!res.ok) return alert('Error al enviar. Intenta de nuevo.');
window.location.href = '/gracias.html';
});Expresiones regulares útiles (México / LatAm)
js
const patrones = {
telefonoMx: /^\d{10}$/,
cp: /^\d{5}$/,
rfc: /^[A-ZÑ&]{3,4}\d{6}[A-Z0-9]{3}$/i,
curp: /^[A-Z]{4}\d{6}[HM][A-Z]{5}[A-Z0-9]\d$/i,
soloLetras: /^[a-zA-ZáéíóúÁÉÍÓÚñÑüÜ\s]+$/,
tarjeta: /^\d{13,19}$/,
placa: /^[A-Z0-9-]{5,9}$/i,
};La validación del frontend NO es seguridad
Cualquiera puede desactivar JavaScript o mandar la petición con Postman. Esta validación es para la experiencia del usuario; siempre repite las reglas en el servidor (PHP o zod en Node).
Accesibilidad
aria-invalid y aria-describedby hacen que un lector de pantalla anuncie "campo no válido" y lea el mensaje de error. aria-live="polite" avisa cuando el mensaje cambia.