Skip to content

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 ​

Validación de formulario

Cómo funciona ​

  1. El formulario lleva novalidate para desactivar los globos del navegador.
  2. Cada campo declara sus reglas con atributos HTML normales.
  3. mensajeDe() lee input.validity (la API de validación del navegador) y traduce cada problema a un mensaje claro.
  4. Reglas extra con atributos data-:
    • data-igual-a="password": debe coincidir con otro campo.
    • data-validar="passwordFuerte": ejecuta una función de reglas.
    • data-mensaje-pattern="...": mensaje para cuando falla pattern.
  5. Se valida al salir del campo (blur) y, una vez tocado, mientras escribe. Así no regañas al usuario antes de tiempo.
  6. 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 ​

AtributoEjemploValida
requiredrequiredQue no esté vacío
typeemail, url, numberFormato
minlength / maxlengthminlength="8"Longitud del texto
min / maxmin="1" max="10"Rango numérico o de fecha
patternpattern="[A-Z]{4}\d{6}"Expresión regular
stepstep="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.

Academia Web — aprende, copia y construye.