¿Como personalizar los option de un elemento select?
Ésta es una de las preguntas cuya respuesta es más ansiada en maquetación web. ¿Cómo le doy estilos al listado de opciones de mi menú desplegable? ¿Cómo puedo sobreescribir los estilos de sistema del option? ¿Cómo puedo eliminar el fondo azul del option seleccionado?
Está claro que la cuestión no tiene fácil solución, de otra forma existirían cientos de artículos explicando cómo hacerlo. En lugar de eso, existen cientos de artículos explicando por qué no es posible. Pero los imperios no los crean emperadores que se rinden ante el primer galo que les planta cara ;). Y el cssar no iba a dejar su suerte en manos de la providencia.
Por ello «vini, vidi, vinci», o lo que es lo mismo, llegé, analicé y experimenté para poderos daros una victoria a modo de solución. O mejor dicho de varias soluciones que os presento a continuación.
Menú desplegable con datalist y options personalizado – Experimento 1
Ésta es la más sencilla de las soluciones. Parte de la idea de que los <option>
de un <datalist>
son áltamente personalizables y por ello los perfectos candidados para iniciar los experimentos sustituyendo el select por un input.
Datalist se utiliza junto con multiplicidad de input types (range, date, etc.) y para asociarlo a ellos, se usa el parámetro list del input. Esto permite que el input tome su valor de la selección que se realice sobre el datalist. Sin embargo tiene un inconveniente. Al igual que con <select>
, el listado ofrecido es un elemento de sistema NO PERSONALIZABLE. Por lo tanto…
Paso 1. Deshacernos del atributo list
Ahora tenemos un input y un datalist oculto por el agente de usuario (el navegador), pero que podremos mostrar de forma sencilla aplicando display: block al recibir el input el foco. Y por supuesto, podremos manipular sus estilos CSS a nuestro antojo.
Paso 2. Devolverle la funcionalidad de select
Al perder la asociación proporcionada por list, deberemos usar algo de javascript (jQuery en este caso), para transferir el valor de datalist al input. Nada complicado podéis verlo en el siguiente codepen.
See the Pen
Personalizar Select Option con CSS – Experimento 1 by Daniel Abril (@elcssar)
on CodePen.
CSS Select option – Experimento 2
En este experimento me ceñí a la estructura estándar de un <select>
para salvaguardar la accesibilidad natural del elemento y permitir que los usuarios siguiesen identificándolo como un select por medio de los lectores de pantalla. Los retos eran los siguientes:
- El primer objetivo era poder dar estilo a los
<option>
- Debía conservar la interacción tanto mediante un ratón como mediante el teclado.
- Debía evitar en la medida de lo posible depender de javascript.
Objetivo 1. CSS para html option
Sólo existe una forma de poder dar estilos CSS a un option html: haciendo que deje de ser un menú desplegable. -«¿Cómo? Pero entonces ya no nos sirve…», diréis-. Pues sí, a veces hay que destruir antes de reconstruir. Más tarde nos preocuparemos de volver a hacer que parezca un dropdown menu. Por lo tanto…
Paso 1. Deconstruir el select
Hay dos maneras de lograr esto: mediante el atributo multiple y/o size. Ambas tienen el mismo efecto sobre el select, cambian su ARIA role de combobox (valor por defecto) a listbox. Mientras combobox identifica un input que controla otro elemento (listbox o grid) que aparece dinámicamente para permitir seleccionar el valor que le aplicará, listbox sería esa lista de opciones desde la que tomaría el valor.
Puesto que no pretendemos crear un multiselect, por lógica la primera manera queda descartada, a menos que queramos complicarnos más la vida.
Así pues, aplicaremos el atributo size. El valor mínimo para lograr nuestro objetivo es dos, pero de momento le pondremos 3 para poder ver mejor el siguiente paso. De momento tendremos un select de 3 líneas en vez de una única línea con caret (la flechita ▼). Si añadiésemos más de 3 options aparecería el scroll .
Le he dado un poco de estilo a la caja para que quedase un poco más presentable.
Paso 2. Dar estilos CSS a option
Ahora ya podemos darle estilos a nuestro option:
- Primero le añadimos margenes con padding
- Seguidamente colores para el texto y el fondo en función del estado: focus, hover…
- Finalemente añadimos unos separadores con border
Paso 3. Eliminar el fondo azul del option
El background azul de :hover ya lo hemos solucionado. Vamos a por el del option seleccionado.
Lo primero que tenemos que entender es que el color azul con texto blanco no corresponde con ningún estado controlable mediante CSS estándar o que podamos forzar desde el depurador de código. Se trata de pseudo-clases propias del navegador a las que no tenemos acceso.
Sin embargo, como sabemos por donde van los tiros, porque vemos que el efecto coincide con el momento en el que se selecciona el elemento, para lo cual CSS nos ofrece el pseudo-selector :checked, podemos partir de ahí. Tendremos que resolver dos problemas: el fondo azul y el texto blanco (aunque esto podría variar según el navegador).
¿Cómo cambiamos el fond azul del option?
Aquí vamos a tirar de un truquito: background-image siempre está por encima de background-color. Y CSS nos permite usar como imagen de fondo funciones como linear-gradient()
. Así, crearemos un degradado entre blanco y blanco (o el color de fondo que queramos), que se superpondrá al color azul.
option:checked {
background: linear-gradient(#fff, #fff);
}
Puesto que el fondo es blanco y el texto también, ahora no podremos leer la etiqueta del option.
Solucionemos el problema del texto blanco
El atributo color del texto en este caso tampoco lo podemos cambiar, como no podíamos cambiar el background-color. Aunque sí podemos cambiar el font-size, font-weight, font-style o text-transform.
Entonces de nuevo tendremos que tirar de imaginación y pensar «out of the box». Html option dispone del atributo label y CSS nos permite crear pseudoelementos :before y :after a cuyo atributo content podemos asignarle un valor mediante la función CSS attr().
La clave será sustituir uno por el otro. Por lo tanto eliminamos el texto del interior del html option y se lo añadimos al label para poder rescatar el valor mediante CSS.
HTML
<option label="mi etiqueta"></option>
CSS
option:checked {
font-size: 0; Ocultamos el label controlado por el navegador y le damos estilos para sus hijos
font-weight: bold;
color: #39C;
background: linear-gradient(#fff, #fff);
}
option:checked:after {
content: attr(label);
font-size: 14px; Recuperamos el tamaño del texto
vertical-align: middle;
}
Para poder centrar el texto he usado un pequeño truco que explico en Alinear texto verticalmente con css vertical-align y flexbox.
Y de esta forma, hemos logrado modificar los CSS de un HTML option.
Objetivo 2. Simular la interacción del select tradicional
Bien, esta fue la parte más compleja, y el objeto principal del experimento: cómo lograr simular con CSS la interacción propia de un menú desplegable que ha dejado de serlo.
Tras muchas combinaciones complejas e infructuosas de :checked, :hover :active, :focus, :focus-within… en las que cada vez que parecía que había logrado que todo funcionase como un select nativo, aparecía un caso de uso en el que se me iba la solución al traste; finalmente, decidí simplificar el problema y dar solución a cada caso por separado. Tomé la determinación de centrarme en montar un dropdown basado en :hover. Sabía que no era lo ideal, pero tenía que avanzar. Y funcionó, tenía un selector bastante funcional para usarios «videntes». Luego abordaría las cuestiones de acccesibilidad.
De esta forma, lo primero que necesité fue añadir un opgroup que utilizaría como menú desplegable. Había considerado otras alternativas como cambiar la altura del propio select, pero había una cuestión importante: el dropdown debía poder superponerse a otros contenidos, o incluso ser posicionable en base al espacio disponible alrededor de la caja del select. Por lo tanto, necesitaba un elmento independiente.
Para que fuese intuitivo además, la caja del dropdown debía incluir el caret y una opción por defecto que invite a seleccionar una opción. Para más detalles de cómo hacer esta parte os invito a que leáis HTML Select CSS dropdown. En él se explica pormenorizadamente cómo dar estilos a la caja del select.
Simular el efecto de selección
En un select estándar, cuando marcas un option su valor se traslada a la caja tras cerrarse la lista desplegable de opciones. Ese efecto es el que perdimos al añadir size y el que recuperaremos a continuación.
Para ello, por defecto ocultaremos todos los option del optgroup, dejando sólo visible un option disabled con el valor –Selecciona una opción–. Al pasar por encima el ratón, mostraremos todos los otros option y una vez marquemos uno, éste sustituirá al primero, que nunca podrá volver a ser marcado, y el resto se ocultarán. Sólo otras opciones del optgroup podran activarse.
Para lograr la magia echaremos mano de una función CSS que recientemente ha logrado soporte casi total entre los navegadores: :has(); Con ella comprobaremos si select contiene algún hijo marcado y ocultaremos el resto.
select:has(option:checked) option:not(:checked) {
display: none;
}
Pero permitiremos que puedan volver a visualizarse con :hover para una nueva selección:
select:has(option:checked):hover option {
display: block;
}
Añadir accesibilidad
Llegados a este punto está claro que sólo podrá ser usado por personas con plena capacidad visual. El uso de :hover hace la interacción dependiente del uso ratón. Para solventarlo amplié los selectores :hover para que incluyesen también :focus y :focus-within.
Para ello usé la función CSS :is(), que nos permite simplificar el selector como vemos a continuación:
select:is(:hover, :focus, :focus-within) optgroup {...}
Si queréis saber más sobre la función :is() podéis hacerlo en el artículo Selectores CSS avanzados 4/4 – :is() y :where().
Des esta forma tanto el select como los option podían ser accesibles mediante la tecla Tab y las flechas de dirección. Sin embargo desaconsejo poner tabindex a los options, eso iría en contra de la navegación estándar y generaría algunos problemas adicionales. Por lo tanto nuestro HTML quedaría tal que así:
<select id="mySelect" tabindex="1" value=0 size=2>
<option disabled>-- Selecciona una opción --</option>
<optgroup>
<option label="Opción 1" value="1"></option>
<option label="Opción 2" value="2"></option>
<option label="Opción 3" value="3"></option>
<option label="Opción 4" value="4"></option>
</optgroup>
</select>
Objetivo 3. Independencia de javascript
Aunque toda la interacción era correcta, consireré un problema el hecho de que al navegar mediante el teclado y seleccionar una opción, el menú desplegable no se cerrase. Por lo tanto tuve que tragarme el orgullo y acceder a usar un poco de javascript para que pasados unos segundos del cambio de valor del select, el desplegable se cerrase.
Para lograrlo básicamente lo que hago es quitar el foco mediante la función jQuery blur().
See the Pen
Personalizar Select multiple con CSS – Experimento 2 by Daniel Abril (@elcssar)
on CodePen.
Espero como siempre que os haya sido útil. No dudéis en dejar vuestros comentarios y suscribíos si os ha gustado para recibir lo último de elcssar.com