CSS :invalid Pseudoclase
Aprende cómo CSS :invalid aplica estilos a campos de formulario que no superan las restricciones de validación, con ejemplos, consejos de accesibilidad y soporte de navegadores.
La pseudoclase CSS :invalid coincide con los controles asociados a formularios — como <input>, <select> y <textarea> — cuyo valor actual no supera las restricciones de validación integradas del navegador. Es la mitad visual de la API de validación de restricciones de HTML: el navegador evalúa la validez, y :invalid te permite aplicar estilos al resultado con CSS puro, sin necesidad de JavaScript.
Esta página explica exactamente cuándo coincide :invalid, cómo aplicar estilos sin alarmar a los usuarios antes de que hayan escrito nada, el comportamiento peculiar de los botones de opción, la alternativa moderna :user-invalid y cómo mantener accesible la retroalimentación de errores.
¿Cuándo coincide un elemento con :invalid?
Un control coincide con :invalid cuando tiene al menos una restricción de validación y su valor actual viola esa restricción. Factores desencadenantes habituales:
| Restricción | Atributo / tipo | Falla cuando… |
|---|---|---|
| Requerido pero vacío | required | el campo no tiene valor |
| Formato de correo incorrecto | type="email" | el valor no es un correo electrónico sintácticamente válido |
| Formato de URL incorrecto | type="url" | el valor no es una URL absoluta válida |
| Fuera de rango | min / max en type="number", type="date", etc. | el valor está fuera del rango permitido |
| Paso incorrecto | step | el valor no se alinea con el intervalo de paso |
| No coincide con el patrón | pattern | el valor no coincide con la expresión regular |
| Demasiado largo / corto | minlength / maxlength | la longitud del valor está fuera del rango permitido |
Si un control no tiene restricciones en absoluto (un <input type="text"> simple sin atributos adicionales), se considera sin restricciones y ni :valid ni :invalid se le aplican.
Un elemento <fieldset> coincide con :invalid cuando alguno de sus controles de formulario descendientes es inválido.
Para el caso contrario, consulta la pseudoclase :valid, que coincide con controles que superan todas las restricciones. La pseudoclase :required coincide con los campos requeridos independientemente de si tienen un valor.
Sintaxis
:invalid {
/* declarations applied to all invalid form controls */
}Limita :invalid a un tipo de elemento específico para mantener los estilos predecibles:
input:invalid,
textarea:invalid {
border: 2px solid #c00;
outline: none;
}Ejemplo básico
El campo de correo electrónico que aparece a continuación está pre-rellenado con una dirección mal formada ("not-an-email"), por lo que coincide con input:invalid al cargar y recibe un borde rojo.
<!DOCTYPE html>
<html>
<head>
<title>:invalid example</title>
<style>
input:invalid {
border: 2px solid #c00;
background-color: #fff0f0;
}
input:valid {
border: 2px solid #090;
background-color: #f0fff0;
}
</style>
</head>
<body>
<h2>:invalid selector example</h2>
<form>
<label for="email">Email:</label>
<input id="email" type="email" value="not-an-email" required />
</form>
</body>
</html>Evitar el "rojo prematuro"
El problema más común con :invalid: un campo required vacío ya es inválido en el momento en que se carga la página, por lo que un formulario nuevo puede mostrarse en rojo antes de que el usuario haya escrito nada. Eso resulta acusatorio.
Opción 1 — solo aplicar estilos mientras el campo está enfocado
Mostrar el borde de error solo mientras el usuario está activamente en el campo:
input:invalid:focus {
border-color: #c00;
outline: 2px solid #c00;
outline-offset: 1px;
}Esto es sencillo, pero desaparece en el momento en que el usuario avanza con el tabulador, por lo que un campo requerido en blanco vuelve a verse bien.
Opción 2 — ocultar el error mientras el marcador de posición esté visible
:placeholder-shown es verdadero cuando se muestra el texto del marcador de posición (es decir, el campo está vacío). Combinarlo con :not hace que el estilo de :invalid solo se active una vez que el usuario haya escrito algo:
/* Only show the error style when the field has a value that is invalid */
input:invalid:not(:placeholder-shown) {
border-color: #c00;
}Esto es eficaz, pero requiere que cada campo tenga un atributo placeholder definido; de lo contrario, :placeholder-shown nunca es verdadero y la protección no hace nada.
Opción 3 — usar :user-invalid (estándar moderno)
La pseudoclase :user-invalid fue diseñada específicamente para resolver este problema. Se comporta como :invalid pero solo coincide después de que el usuario haya interactuado con el control (escrito en él, salido de él o enviado el formulario):
/* Supported in all modern browsers as of 2024 */
input:user-invalid {
border-color: #c00;
}
/* Fallback for older browsers */
@supports not selector(:user-invalid) {
input:invalid:not(:placeholder-shown) {
border-color: #c00;
}
}:user-invalid es la solución más limpia cuando puedes depender de ella. Firefox la ha soportado como :-moz-ui-invalid durante años; el estándar :user-invalid está ahora en todos los navegadores modernos.
Enfoque de estilos
Un borde rojo completo de 2 px es legible pero chocante. Considera combinar un cambio de borde con una sutil box-shadow para un aspecto más suave:
input:invalid:not(:placeholder-shown) {
border-color: #c00;
box-shadow: 0 0 0 3px rgba(204, 0, 0, 0.15);
}Evita depender únicamente del color; consulta la sección de Accesibilidad más abajo.
Peculiaridades
Botones de opción
Cuando un grupo de botones de opción tiene required en una de sus entradas, cada botón del grupo coincide con :invalid mientras ninguno esté seleccionado. Aplicar estilos a pequeños círculos de radio no es práctico; en su lugar, aplica estilos al <fieldset> o <label> que los rodea:
/* Style the fieldset, not the radio buttons themselves */
fieldset:invalid {
border: 2px solid #c00;
border-radius: 4px;
padding: 8px 12px;
}Todos los botones de opción de un grupo comparten el mismo atributo name — eso es lo que los convierte en un grupo dentro del modelo de validez del navegador.
Campos opcionales vacíos
Un <input type="text"> simple sin required, sin pattern y sin restricciones de longitud siempre es :valid aunque esté vacío. :invalid solo se activa cuando existe una restricción y se viola.
select y textarea
<select> coincide con :invalid si es required y su value actual es una cadena vacía (un patrón común es una <option value=""> de marcador de posición "-- elegir --" al inicio). <textarea> sigue las mismas reglas que <input> para required, minlength y maxlength.
Firefox y :-moz-ui-invalid
Firefox ha aplicado durante mucho tiempo estilos mediante :-moz-ui-invalid, que solo se activa después de la interacción del usuario, lo que efectivamente incorpora el comportamiento de :user-invalid. Si añades tus propias reglas :invalid y las pruebas en Firefox, el campo puede verse bien (porque la protección de interacción del usuario predeterminada del navegador está activada), y luego comportarse de manera diferente en Chrome (donde la protección predeterminada está desactivada). Define reglas explícitas y usa :user-invalid con un respaldo para obtener un comportamiento coherente.
Accesibilidad
El color solo nunca es suficiente para comunicar un error: los usuarios con deficiencias en la visión del color pueden no percibir un borde rojo. Combina el estilo de :invalid con:
- Un mensaje de texto visible que explique qué salió mal y cómo solucionarlo.
- Un icono o símbolo junto al cambio de color (por ejemplo, una ✕ o un icono de advertencia).
aria-invalid="true"en el control para que los lectores de pantalla lo anuncien como inválido.aria-describedbyapuntando al elemento de mensaje de error para que la descripción se lea automáticamente.
<label for="email">Email address</label>
<input
id="email"
type="email"
aria-invalid="true"
aria-describedby="email-error"
required
/>
<span id="email-error" role="alert">
Please enter a valid email address.
</span>El role="alert" en el span de error hace que los lectores de pantalla anuncien el mensaje en cuanto aparece en el DOM, incluso sin enfoque.
Pseudoclases relacionadas
| Pseudoclase | Coincide cuando… |
|---|---|
:valid | el control supera todas sus restricciones |
:required | el control tiene el atributo required |
:optional | el control no tiene required |
:out-of-range | el valor de una entrada numérica/de fecha supera min/max |
:in-range | el valor de una entrada numérica/de fecha está dentro de min/max |
:placeholder | el texto del marcador de posición de una entrada |
:focus | el control tiene actualmente el foco de teclado |
Compatibilidad con navegadores
:invalid es parte de Selectors Level 4 y ha sido compatible con todos los principales navegadores durante muchos años. :user-invalid (la variante que tiene en cuenta la interacción) llegó en Chrome 119, Firefox 88 (como :-moz-ui-invalid mucho antes) y Safari 16.5.
Para más información sobre los atributos de restricción nativos de HTML, consulta <input> y HTML Forms.