Si eres desarrollador/a de CSS estarás muy habituada/o a utilizar con maestría las pseudo-clases. Pero ¿conoces todas las que hay? ¿las usas todas?, ¿les sacas todo el potencial que pueden darte?

Sabemos que las pseudo-clases no son los selectores CSS con mejor performance, más bien al contrario, pero nos permiten manejar y mantener mejor nuestro código y es aconsejable tener consenso en equipos grandes sobre su utilización e impacto en el desarrollo de la arquitectura CSS de nuestros proyectos.

Pseudo-clases vs pseudo-elementos

Para quienes no estéis familiarizados/as o estéis empezando a desarrollar con CSS es probable que no tengáis muy claro qué diferencia hay entre las pseudo-clases y los pseudo-elementos:

  • Las pseudo-clases son palabras clave que añadimos a nuestros selectores CSS para especificar un estado sobre el elemento seleccionado. Por ejemplo, si usamos la pseudo-clase :active estaremos aplicando un estilo concreto al elemento cuando éste se encuentre en un estado activo.
  • Los pseudo-elementos también son palabras clave que añadimos a nuestros selectores CSS pero, en lugar de especificar un estado sobre el elemento al que se lo aplicamos, crean un elemento nuevo HTML y en ocasiones nos permiten incluir contenido con ellos.

Y lo más importante: su sintaxis es distinta. Esto es muy importante ya que en la antigua especificación de CSS los pseudo-elementos se escribían con la misma sintaxis que las pseudo-clases y eso ha generado “malos hábitos” en algunos/as desarrolladores/as que siguen escribiéndolos “a la vieja usanza” (cosa fácilmente evitable si utilizamos un linter para nuestro CSS,) por eso es importante que recordemos que:

// Los pseudo-elementos se escriben con doble dos puntos
.mi-clase::after {}
.mi-clase::before {}

// Las pseudo-clases se escriben con sólo un doble punto
.mi-clase:hover {}
.mi-clase:not {}

Pseudo-clases

Muchas de ellas las utilizamos en nuestro día a día de manera automática y por eso solemos creer que controlamos completamente este tema. Pero hay algunas pseudo-clases más “desconocidas” que utilizamos poco (o directamente no utilizamos) que nos podrían evitar muchos quebraderos de cabeza. No os miento si os digo que el día que descubrí la pseudo-clase :not cambió mi vida.

Las vamos a dividir en subgrupos en función de su relación con los elementos del DOM y las interacciones de usuario.

  1. Basadas con la interacción del usuario con el elemento al que aplican.

Estas son probablemente las más conocidas y usadas, incluso por aquellos/as que no estáis muy familiarizados con CSS:

  • :hover
  • :active
  • :focus, :focus-visible y :focus-within: durante mucho tiempo como desarrolladora he tenido que eliminar los estilos asociados a la interacción “focus” por especificaciones de diseño, lo cual es una muy mala práctica de cara a la accesibilidad de nuestros desarrollos. Por ello, os recomiendo que cuando estéis ante diseños que no tienen en cuenta estos estilos, habléis con el equipo de diseño para que os den unas directrices sobre ellos y poder así mantener los mínimos estándares de accesibilidad en nuestros desarrollos. Aquí tenéis una muy buena explicación de la importancia de estos estilos.
  1. Para elementos de tipo link que hay presentes en el DOM: :any-link, :link, :visited, :local-link, :target, :target-within, :scope
  2. Para elementos que tienen un estado o lugar en la línea temporal su ejecución. Puede que esto os suene raro, pero se aplican a elementos multimedia: :paused, :playing, :current, :past, :future
  3. Relacionadas con el idioma del desarrollo:
  • :dir, esta etiqueta solo tiene soporte en Firefox, por lo que no es de gran utilidad a día de hoy. No obstante, si llegara a estandarizarse para todos los navegadores, en aquellas apps multilenguaje donde en ocasiones tenemos que hacer mirroring del texto esta sería la solución ideal.
  • :lang, esta etiqueta puede salvarte cuando tienes idiomas que “no encajan” en la maqueta porque tienen palabras demasiado largas (como suele suceder con el alemán, por ejemplo). Obviamente depende de que el multiidioma en nuestra aplicación esté correctamente aplicado en los metadatos.
  1. Basadas en la estructura del DOM.

Estas pseudo-clases también suelen ser muy utilizadas y el listado es largo. Hacen referencia al lugar que ocupa el elemento sobre el que queremos aplicar el estilo en el DOM.

  • :root: actualmente nos es más familiar, ya que en ella solemos definir nuestras variables nativas. Esta pseudo-clase hace referencia a la raíz del documento que no es otra que la etiqueta html del mismo, pero tiene mayor especificidad.
  • :first-child, :last-child,:nth-child(), :nth-last-child(): son pseudo-clases que hacen referencia a la posición de los hijos de determinado tipo que tiene el elemento al que estamos referenciando. En el siguiente ejemplo tenemos una serie de párrafos que queremos dar estilos de manera diferenciada entre ellos y para ello podemos usar estas pseudo-clases basándonos en el orden que ocupan dentro del div padre:
< !-- Para el selector .parent p las pseudo-clases que debemos usar para cada elemento son: -->
<div class="parent">
  Child 1 // :first-child
  Child 2 // :nth-child(2)
  Child 3 // :nth-child(3)
  Child 4 // :nth-child(4)
  Child 5 // :last-child
</div>

nth-child() además de admitir el valor de la posición del elemento que queremos estilar, podemos utilizar even/odd para elegir los pares/impares o expresiones matemáticas como (2n +3) para selecciones más complejas. Podéis verlo más claro en el siguiente ejemplo:

See the Pen MWmNgNv by @NoemiMS on CodePen.

Si no dais con la fórmula que necesitáis para vuestra selección, podéis consultar este post en el que hay multitud de ejemplos aritméticos.

Por último, :nth-last-child() tiene el mismo comportamiento que nth-child pero empezando a contar desde el último elemento.

<div class="parent">
  Child 1 
  Child 2 // :nth-last-child(3)
  Child 3 // :nth-last-child(2)
  Child 4 // :nth-last-child(1)
</div>
  • :first-of-type: :last-of-type, :nth-of-type(), :nth-last-of-type(): al igual que las pseudo-clases del punto anterior, harán referencia a los hijos de un padre en concreto pero esta vez basándose en el tipo de etiqueta al que estamos referenciando y no a su posición.
<div class="parent">
  <div>Child</div>  // .parent div:first-of-type
  Child  
  <div>Child</div> // .parent div:last-of-type
  Child
 <h3> Child </h3>
</div>

:nth-of-type() y :nth-last-of-type() funcionan igual que :nth-child pero además de poder elegir la posición que ocupa el elemento podemos discriminar por el tipo de elemento. :nth-last-of-type() , es lo mismo pero empezando a contar desde el último elemento que coincida.

<div class="parent">
  <div>Child</div>  // .parent div:nth-of-type(1)
  Child  
  <div>Child</div>  // .parent div.nth-of-type(2)
  Child
 <h3> Child </h3>
<div>Child</div>  // .parent div:nth-last-of-type(2)
  Child  
  <div>Child</div> // .parent div:nth-last-of-type(1)
</div>

Para las pseudo-clases que admiten expresiones matemáticas, este tester también nos viene muy bien para poder hacer pruebas y encontrar el selector que más nos convenga.

  1. Para elementos de formulario

Son pseudo-clases que hacen referencia a estados de los distintos elementos que podemos encontrar en un formulario.

  • :enabled, :disabled: enabled seleccionará los elementos que no tengan el atributo disabled definido.
  • :optional, :required: optional seleccionará los elementos que no tengan el atributo required.
  • :checked: esta es la joya de la corona de las pseudo-clases de formularios porque gracias a ella y unos pequeños conjuros de css podemos hacer checkboxes personalizados (entre otras muchas cosas).
  1. Basadas en las relaciones entre elementos o selectores

Estas pseudo-clases están basadas en relaciones “lógicas” entre los elementos a los que aplican.

  • :not(): si leemos el uso de esta regla, queda bastante claro para qué sirve, aplicará los estilos a aquellos elementos que no sean los que listemos entre paréntesis
.parent :not( p, .title) {
   color: var(--c-primary);
}

Si aplicaramos estos estilos al siguiente código html, sólo los h3 tendrían como color de texto el color primario:

See the Pen qBXaWmz by @NoemiMS on CodePen.

  • :is(): podríamos decir que es la antagonista de :not, ya que permite seleccionar los elementos sobre los que queremos aplicar los estilos. Lo que la diferencia del todo con :not(), es que no tiene soporte en IE.
  • :where(): es exactamente como :is() pero su especificidad siempre es 0.

See the Pen eYWqjrK by @NoemiMS on CodePen.

  • :empty: nos permite seleccionar los elementos que estén vacíos, tanto de contenido como de hijos. No tiene soporte en IE.
  • :has(): no puedo resistirme a incluir esta pseudo-clase a pesar de quees totalmente experimental a día de hoy, queremos que se estandarice y comenzar a usarla. Sería la forma de tener un “selector de padre” directamente en CSS y muy probablemente cambie nuestra forma de desarrollarlo y de pensar. Os dejo un par de ejemplos muy sencillos para que entendáis el hype:
div:has(h3){
  background: purple;
}

// Con este código un div tendrá fondo naranja  solo si tiene un h3 dentro

div:has( + div){
  background: purple;
}

// Con este código sólo tendrían un fondo púrpura los divs que tuvieran inmediatamente dentro de ellos otro div

Conclusión

A pesar de que comentábamos al principio del post que no son los selectores CSS con mejor performance, no debemos olvidarnos de ellas porque son herramientas que nos facilitan mucho la vida cuando con un simple :not() o un .nth-child() nos ahorramos tener que llenar nuestro HTML de clases.

Además, como hemos visto también, estas pseudo-clases siguen evolucionando y se siguen proponiendo nuevas como en el caso de :has() por lo que espero que después de leer este post podáis usarlas algo más.

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.