Cómo controlar :invalid en CSS y mostrar errores solo tras el submit

- Andrés Cruz - EN In english

Cómo controlar :invalid en CSS y mostrar errores solo tras el submit

Uno de los problemas más comunes al trabajar con validación HTML5 es que los campos obligatorios aparecen visualmente como inválidos desde el primer render de la página, incluso antes de que el usuario haya interactuado con el formulario. En formularios reales, este comportamiento no solo es confuso: genera una mala experiencia de usuario que puede aumentar el abandono del formulario.

En este artículo te explico por qué ocurre, cómo funciona realmente :invalid en HTML5 y, sobre todo, cómo mostrar errores visuales en los inputs solo después de hacer submit, usando CSS moderno y, si hace falta, un pequeño fallback con JavaScript.

¿Qué ocurre realmente cuando un input es :invalid en HTML5?

La validación de formularios en HTML5 funciona mediante el sistema de constraint validation. El navegador evalúa cada campo según sus atributos (required, type, pattern, min, max, etc.) y determina si el valor cumple las restricciones definidas. Si no las cumple, el campo entra en estado inválido de inmediato.

Cuándo se activa :invalid y por qué aparece al cargar la página

Un punto clave que suele sorprender a quien se enfrenta a esto por primera vez:

Un <input required> vacío ya es inválido desde el momento en que la página carga, aunque el usuario no haya tocado nada.

Por eso, aplicar esta regla CSS:

input:invalid {
  border: 2px solid red;
}

…provoca que los campos aparezcan en rojo incluso antes de que el usuario escriba una sola letra. En mi caso, este fue el primer problema real que encontré: la validación funcionaba correctamente, pero el timing era pésimo.

El navegador no distingue si el usuario "aún no ha empezado" o si "ya cometió un error". Para él, el campo simplemente no cumple las reglas del atributo required.

Validación visual de campos requeridos con CSS

Con HTML5 podemos utilizar el atributo required para indicarle al navegador que un campo es obligatorio. A partir de ahí, podemos aprovechar la presencia de ese atributo directamente en CSS para dar retroalimentación visual al usuario, sin necesidad de JavaScript.

<form>
  <input required />
  <input type="submit" />
</form>

Si el campo está vacío y el usuario intenta enviar el formulario, el navegador cancelará el envío automáticamente y mostrará su mensaje de validación nativo:

Formulario con mensaje de error de validación HTML5

Intenta enviar el formulario vacío y verás el mensaje de error del navegador.

Si queremos personalizar visualmente los campos que tienen el atributo required —por ejemplo, mostrando un borde amarillo de advertencia— podemos usar la pseudo-clase :required en CSS:

/* Campos requeridos: borde amarillo de advertencia */
form input:required {
  border: 2px solid yellow;
}

Diferencia entre :valid e :invalid

Podemos ir un paso más allá y combinar ambas pseudo-clases para dar feedback diferenciado según el estado del campo:

input:valid {
  border: 2px solid green;
}
input:focus:invalid {
  border: 2px solid red;
}

Aquí aparece una idea importante: condicionar :invalid al foco del campo con :focus. Así, el campo solo se marca en rojo mientras el usuario está interactuando con él, evitando que aparezca en rojo al cargar la página.

Pero aún había un problema: este enfoque no cubre el caso del submit fallido.

El problema UX: errores visibles antes de interactuar

Aunque :focus:invalid evita mostrar errores al cargar la página, no cubre completamente el escenario más crítico:

  • El usuario no toca ningún campo.
  • Pulsa directamente "Enviar".
  • El navegador bloquea el envío.
  • Pero visualmente… no pasa nada en el formulario.

Desde el punto de vista del usuario, el formulario "no funciona". Aquí entendí que el problema no era la validación en sí, sino cuándo mostrar el feedback visual.

Por qué marcar inputs vacíos como inválidos es mala experiencia

Los errores de validación deberían aparecer únicamente cuando:

  • El usuario intenta enviar el formulario, o
  • ya interactuó con un campo y lo dejó con un valor incorrecto.

Nunca antes. Mostrar bordes rojos en inputs vacíos transmite una sensación de error constante, incluso cuando el usuario todavía no ha hecho nada mal. Es como recibir un mensaje de error en un formulario antes de haber escrito una sola letra.

Ejemplo completo con :focus:invalid

Usando pseudo-clases podemos hacer cosas realmente interesantes: asignar color verde al input cuando el valor es válido y rojo cuando no lo es, pero solo mientras el usuario esté editando ese campo (con foco activo):

/* Campo de email válido: verde */
form input[type="email"]:required:valid {
  border: 2px solid green;
}
/* Campo de email inválido mientras tiene foco: rojo */
form input[type="email"]:focus:required:invalid {
  border: 2px solid red;
}

El motivo de combinar :focus antes de :invalid es que, al cargar la página, el campo está vacío y por tanto ya tiene estado :invalid por defecto. Añadir :focus asegura que ese estado rojo solo sea visible cuando el usuario está dentro del campo.

Formulario completo con validación visual en CSS

Un ejemplo más completo, con campos obligatorios y opcionales combinados:

/* Campos requeridos: advertencia amarilla */
form input:required {
  border: 2px solid yellow;
}
/* Campo con valor válido: verde */
form input:valid {
  border: 2px solid green;
}
/* Campo con valor inválido mientras tiene foco: rojo */
form input:focus:invalid {
  border: 2px solid red;
}
<form>
  <input required type="text"  name="nombre"  placeholder="Primer nombre" />
  <input          type="text"  name="nombre2" placeholder="Segundo nombre" />
  <input required type="email" name="email"   placeholder="Su correo electrónico" />
  <input required type="tel"   name="tel"     placeholder="Su teléfono" />
  <input          type="url"   name="url"     placeholder="Su página web" />
</form>

Los campos obligatorios (nombre, email y teléfono) mostrarán borde amarillo en reposo, verde si el valor es válido y rojo únicamente cuando el usuario está escribiendo un valor incorrecto. Los campos opcionales (segundo nombre y página web) solo mostrarán verde si contienen un valor válido.

Usando :user-invalid (la solución correcta)

La pseudo-clase :user-invalid fue diseñada exactamente para resolver este problema. A diferencia de :invalid, que se evalúa desde el momento en que la página carga, :user-invalid solo se activa cuando:

  • El usuario ha interactuado con el campo y lo ha dejado inválido, o
  • ha intentado enviar el formulario al menos una vez.
input:user-invalid {
  border: 2px solid red;
}

Esto evita el clásico problema de inputs en rojo al cargar la página. Cuando descubrí esta pseudo-clase, fue el punto de inflexión: el comportamiento encajaba exactamente con lo que espera cualquier usuario.

:user-invalid es la mejor forma de mostrar errores de validación solo después del submit usando CSS puro, sin una sola línea de JavaScript.

Es una pseudo-clase moderna con buena compatibilidad en navegadores actuales (Chrome 119+, Firefox 88+, Safari 16.5+), pero sin soporte en versiones muy antiguas. Para esos casos existe el fallback que veremos a continuación.

Compatibilidad y alternativas cuando :user-invalid no está disponible

Si necesitas compatibilidad total con navegadores más antiguos, o simplemente quieres más control sobre cuándo se activa el estado de error, hay un patrón muy simple y efectivo basado en JavaScript y una clase CSS.

Fallback con JavaScript y la clase .submitted

JavaScript:

document.querySelector("form").addEventListener("submit", function () {
  this.classList.add("submitted");
});

CSS:

/* Por defecto: estilo neutro en inputs inválidos */
input:invalid {
  border: 1px solid #ccc;
}
/* Solo después del submit: resaltar errores en rojo */
form.submitted input:invalid {
  border: 2px solid red;
}

Este enfoque es muy robusto y predecible:

  • Antes del submit: los campos tienen estilos neutros, sin bordes de error.
  • Después del submit: los campos inválidos se resaltan en rojo.

En proyectos reales donde necesito garantizar compatibilidad amplia, sigo recurriendo a este patrón porque es claro, fácil de mantener y no depende de pseudo-clases experimentales.

Buenas prácticas de validación visual en formularios

  • Cuándo usar solo CSS
    • Formularios simples con pocos campos.
    • Validación básica con required, type="email" o pattern.
    • Proyectos dirigidos a navegadores modernos.
    • UX clara que no requiere mensajes de error personalizados.
  • Cuándo necesitas JavaScript
    • Mensajes de error personalizados por campo.
    • Validaciones complejas o cruzadas entre campos.
    • Compatibilidad con navegadores legacy.
    • Lógica condicional entre campos (por ejemplo, "si A está marcado, B es obligatorio").
  • El orden correcto de capas:
    • HTML primero: define la estructura y las restricciones con atributos nativos.
    • CSS para el feedback visual: :valid, :invalid, :user-invalid.
    • JavaScript solo como mejora progresiva: para mensajes personalizados o compatibilidad.

Preguntas frecuentes sobre validación de formularios con CSS

  • ¿Por qué :invalid se activa al cargar la página?
    • Porque un campo required vacío ya incumple las reglas de validación según el estándar HTML5. El navegador no espera a que el usuario interactúe.
  • ¿Cuál es la diferencia entre :invalid y :user-invalid?
    • :invalid depende únicamente de si el valor cumple las reglas del campo.
    • :user-invalid depende además de la interacción del usuario o del intento de envío.
  • ¿Se puede hacer validación visual sin JavaScript?
    • Sí, completamente, usando pseudo-clases CSS como :valid, :invalid, :user-invalid y combinaciones con :focus. JavaScript solo es necesario como fallback para compatibilidad o para personalizar mensajes de error.
  • ¿Es recomendable ocultar errores hasta el submit?
    • Sí. En mi experiencia, mejora significativamente la percepción del formulario y reduce la frustración del usuario, especialmente en formularios largos o con validaciones complejas.
  • ¿Qué pasa si el usuario corrige el error después del submit?
    • Con el enfoque de la clase .submitted, el error seguirá visible hasta que el campo sea válido (en ese momento input:invalid deja de aplicarse). Con :user-invalid, el navegador gestiona esto automáticamente.

Conclusión

El verdadero reto de la validación de formularios no está en la lógica de validación, sino en cuándo mostrar el error.

Usar :invalid sin más contexto suele provocar una experiencia frustrante. En cambio:

  • :focus:invalid mejora el feedback en tiempo real mientras el usuario escribe.
  • :user-invalid ofrece el comportamiento ideal: errores visibles solo tras el submit o la interacción.
  • Un pequeño fallback con JavaScript y la clase .submitted garantiza compatibilidad total.

Si controlas el timing del feedback visual, tus formularios no solo funcionarán mejor: se sentirán mejor.

Aprende a realizar validaciones sobre formularios con solo HTML y sin JavaScript.

Aprende a controlar input:invalid en CSS con HTML5: usa :user-invalid y un fallback en JavaScript para mostrar errores de validación solo después del submit. Mejor UX garantizada.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

Acepto recibir anuncios de interes sobre este Blog.