En este capítulo sobre selectores avanzados de CSS vamos a ver algunos selectores de validación que combinados con la pseudo clase CSS :valid, o :invalid nos permitirán crear una interfaz da validación más intuitiva para el usuario. Crearemos algunos ejemplos aprovechándonos también de la validación nativa que nos proporcionan los input type de HTML5 o el atributo pattern que ilustrarán mejor el funcionamiento de CSS :valid.
Vamos pues con unas definiciones básicas y seguimos con ejemplos prácticos
CSS :valid / :invalid
Estas pseudo-clases seleccionan todo elemento de formulario capaz de contener un valor susceptible de ser validado. Esta validación podrá ser de varios tipos en función de una serie de atributos del elemento validado.
input[type]:valid / :invalid
HTML 5 nos brinda una gran referencia de validación con sus input type mostrando mensajes específicos para cada tipo de input al pulsar el botón submit. Sin embargo con :valid podemos adelantarnos y crear nuestra propia validación en línea al instante. En el momento en que el usuario entre un valor que no cumpla con lo que HTML5 considera válido, el estado del input cambiará y CSS lo detectará, aplicando el selector que le corresponda.
De la misma forma podemos incluir un parámetro pattern que sea quién defina la cadena contra la que tenemos que comparar el valor.
Otros selectores CSS de validación
:required:valid
Antes incluso que comparar valores, la forma más sencilla de validar un campo es comprobar si está completado o no en caso de ser obligatorio. Esto lo podemos hacer añadiendo el atributo required a nuestro input, y automáticamente :valid e :invalid harán su magia.
:optional
Su opuesto es :optional, que nos indica que el campo no es obligatorio, es decir no está declarado el atributo required, pero su valor puede seguir siendo susceptible de ser validado. Si quieres ahorrarle trabajo a tu usuario, indicarle que el campo es opcional es una buena forma.
:indeterminate
Algunos tipos de input como radio o checkbox, disponen de un estado booleano: checked. Ésos activarán el selector :invalid si incluyen el atributo HTML required mientras el checkbox no esté marcado (checked) o no se haya seleccionado uno de los radio buttons del mismo grupo, es decir, que estén en un estado «indeterminado».
Por lo tanto, en el caso de los radio buttons, podríamos usar :indeterminate en lugar de :required:invalid
Para los checkbox, puede forzarse este estado, o propiedad, mediante el uso de Javascript o jQuery. Aunque me cuesta imaginar una situación donde tuviese sentido.
Otro elemento que dispone del estado :indeterminate es <progress>
. Sin embargo al ser un elemento tipo output, :valid o :invalid, no tienen efecto.
:out-of-range
Seleccionará cualquier input que, como type number o type date, soporte los atributos min y max, y reciba un valor fuera del rango establecido por éstos.
Estilos CSS para inputs :valid e :invalid
Así, vemos que tenemos varias formas de seleccionar ítems de formulario que no son válidos. Por un lado tenemos :invalid para usar con input types de HTML5, REGEX y/o :required; por otro está :indeterminate para, por ejemplo, radio buttons -hay que pensar que marcar un valor por defecto puede condicionar la respuesta del usuario, así que en ocasiones es mejor no marcar ninguno y obligar al usuario a tomar la decisión (aunque eso suene mal a priori)-; y finalmente tenemos :out-of-range que aplica a cualquier input para el que pueda definirse un rango (números, fechas, etc…).
Puesto que cada uno de esos tipos es muy diferente y podrían dar pie a diseños de validación muy diversos o requerirían técnicas más complejas y específicas, hoy nos vamos a centrar en unos estilos genéricos que funcionen para todos, o la mayoría de ellos. Basándonos en algunos criterios de diseño para validación de formularios, construiremos un ejemplo con HTML5, CSS3 y un poco de JS, plasmando esas recomendaciones de diseño.
Validación HTML5
Para que la validación HTML5 sea efectiva, necesitaremos los siguientes elementos:
- Un elemento form que contenga los campos de formulario.
- Elementos input con atributo type u otros elementos de formulario con el atributo required. De esta forma el navegador podrá evaluar los campos en base a las reglas de validación de HTML5 y aplicar los estilos CSS definidos mediante :valid, :invalid, etc.
- Un button o input con type submit. Que ejecutará la validación completa de los elementos contenidos en el formulario.
Este sería el formulario base sobre el que luego añadiremos los elementos de validación.
<label>URL
<input type="url" required/>
</label>
<button type="submit">Validar</button>
</form>
Quedaría algo así:
Sin embargo necesitaremos algunos elementos extra que nos permitan mostrar los mensajes de feedback, sean gráficos o de texto. Por ello encerraremos el texto del label en un <span>
que albergará el check de confirmación y añadiremos un <div>
para contener el mensaje de error
<form id="form">
<label>
<input type="url" required/>
<span class="label">URL</span>
<div class="errorMSG">Introduce una url que empiece por http:// o https://</div>
</label>
<button type="submit">Validar</button>
</form>
Los labels pueden estar declarados de forma explícita mediante el uso del atributo for o implícita, tal como muestra el ejemplo anterior. La razón de usar el método implícito es que nos proporciona un contenedor para los elementos de validación.
Sin embargo, para poder hacer la magia mediante CSS hemos de poner el <span>
que contiene la etiqueta (label) a continuación del <input>
, lo que resulta en una pequeña incidencia de accesibilidad que resolveremos al final del artículo en el apartado dedicado a la accesibilidad.
:valid :invalid en acción
Estilos para :invalid
Ya que tenemos el elemento errorMSG vamos a empezar por :invalid para mostrarlo. En primer lugar lo escondemos:
.errorMSG {
display: none;
}
De esta forma sólo se mostrará cuando el mensaje venga precedido por un campo en estado :invalid, cosa que logramos gracias a la virgulilla (~):
:invalid ~ .errorMSG {
display: block;
}
Ahora que ya lo hemos mostrado le podemos aplicar algunos estilos:
:invalid ~ .errorMSG {
display: block;
width: 180px; /*le damos el mismo tamaño que el input*/
font-size: 12px; /*reducimos el tamaño de la fuente*/
line-height: 1.5;
color: red; /*lo coloreamos de rojo*/
}
Listo, ya tendríamos nuestro mensaje de error. pero aún nos quedan el label y el propio input:invalid. Para empezar reubicamos el label para que aparezca sobre el campo, en una posición similar a la de Material Design de Google.
label {
position: relative; /*nos proporciona los límites para el span que contiene el texto del label*/
display: inline-block; /*permite alinear dos campos o elementos seguidos (p.ej. el botón)*/
margin: 12px 6px;
vertical-align: top; /*Alinea los campos*/
}
.label { /*posicionamos la etiqueta sobre el input*/
position: absolute;
left: 12px;
top: -12px;
color: gray;
background: #fff; /*le damos un fondo para que el texto no se entremezcle con el borde del input*/
}
Una vez lo tenemos posicionado marcamos con rojo el label y el borde del input cuando sean inválidos.
:invalid + .label { /* ":invalid +" indica inmediatamente a continuación de :invalid*/
color: red;
font-weight: 600;
}
:invalid {
border-color: red; /*coloreamos el borde del input:invalid de rojo*/
}
Estilos para :valid
Para el caso de ser válido crearemos un :pseudo-elemento :after con forma de «checked» que posicionaremos junto al texto.
:valid + .label:after { /* ":valid +" indica inmediatamente a continuación de :valid*/
content: "";
/* Posicionamos el elemento en absoluto que a su vez convierte al :pseudo-elemento en block permitiéndonos posicionarlo y dimensionarlo*/
position: absolute;
top: 2px;
/* Creamos los dos lados el símbolo tipo "checked" y seguidamente lo rotamos para dejarlo en su posición natural*/
width: 10px;
height: 5px;
border: solid 3px blue;
border-width: 0 0 3px 3px;
transform: rotate(-45deg);
}
Y con esto tendríamos ya nuestra validación funcional… bueno, casi. Antes de dar el capítulo por cerrado deberíamos resolver algunos problemas de usabilidad y accesibilidad.
Usabilidad en el diseño de validación de formularios web
Aunque el diseño que hemos visto sigue unos criterios rigurosos de usabilidad, queda por resolver una cuestión relacionada con el momento en que mostramos dichos elementos.
No es nada recomendable mostrar mensajes de error al usuario, sean textuales o gráficos, antes incluso de que haya completado los campos. Por esa razón añadiremos la clase validate mediante JS a nuestros campos de formulario en el momento en que cambie su valor, es decir onChange:
//Cuando cambia el valor del campo le añadimos la clase "validate"
$("input").on("change", function() {
$(this).addClass('validate');
$(this).is(:invalid).addClass('validate');
});
//Al pulsar en el botón submit le añadimos la clase "validate" a todos los input del form
$('[type="submit"]').on("click", function(){
$('#form input').addClass('validate');
});
Y la añadiremos también a nuestros selectores para que apliquen sólo una vez el usuario haya terminado de introducir el valor.
.validate:invalid + .label {
color: red;
font-weight: 600;
}
.validate:invalid {
border-color: red;
}
Accesibilidad para campos de formulario con validación
Tanto el texto del label como el mensaje de error se encuentran algo desvinculados del input. Para resolver esto deberemos en primer lugar asignar la etiqueta al campo, pero en este caso no con el atributo for, que es propio del elemento <label>
, sino mediante un atributo aria-labelledby en el elemento <input>
, que se asociará con el id del <span>
que contiene nuestro etiqueta a continuación del <input>
.
<input type="url" aria-labelledby="urlLabel" required/>
<span id="urlLabel" class="label">URL</span>
Seguidamente asociamos al input el mensaje de error mediante el atributo aria-describedby.
<input type="url" aria-labelledby="urlLabel" aria-describedby="urlError" required/>
<span id="urlLabel" class="label">URL</span>
<div id="urlError" class="errorMSG">Introduce una url que empiece por http:// o https://</div>
Finalmente, sólo faltará indicar al lector de pantalla que el valor del campo es inválido. Para ello aprovecharemos la lógica que ya tenemos e incluiremos en dicho campo el atributo aria-invalid=true | false.
$("input").on("change", function() {
$(this).addClass('validate');
var isInvalid = $(this).is(':invalid'); //Creamos una variable booleana true/false
$(this).attr('aria-invalid', isInvalid); //Asignamos la variable al atributo aria-invalid del input modificado
});
Y así logramos que nuestra validación sea completamente accesible para lectores de pantalla además de tener un diseño usable y ofrecer una buena experiencia al usuario.
Podéis ver el código completo en este codepen:
See the Pen
Input :valid :invalid by Daniel Abril (@elcssar)
on CodePen.
Espero que os sea útil para vuestros proyectos. Como siempre, dudas y comentarios son bienvenidos y, si os habéis quedado con ganas de más, podéis leer el siguiente y último artículo de la serie: selectores css avanzados :is() y :where()