CSSSelectores

Pseudo-clases

Seleccionan elementos según un estado o una posición, cosas que no se pueden expresar con una clase o un tipo normal.

¿Qué son?

Una pseudo-clase se escribe con dos puntos (:nombre) y describe un estado especial de un elemento: que el mouse esté encima, que sea el primero de su tipo, que un checkbox esté marcado. No agregan nada al HTML, y no se pueden lograr con un selector normal porque dependen de algo dinámico o de la posición.

Pseudo-clases de interacción

a:hover {
  text-decoration: underline;
}

button:focus {
  outline: 2px solid #5b9bd5;
}

button:active {
  transform: scale(0.97);
}

input:disabled {
  opacity: 0.5;
}

:hover aplica mientras el cursor está encima, :focus mientras el elemento tiene el foco del teclado, :active mientras se lo está presionando, y :disabled mientras el elemento está deshabilitado.

Pseudo-clases de posición

li:first-child {
  font-weight: 600;
}

li:last-child {
  border-bottom: none;
}

tr:nth-child(even) {
  background: #f5f5f5;
}

li:nth-child(3n) {
  color: #5b9bd5;
}

:first-child y :last-child apuntan al primer y último elemento entre sus hermanos. :nth-child() es más flexible: acepta un número fijo, palabras clave como even/odd, o una fórmula como 3n para seleccionar uno de cada tres.

La fórmula an + b

Por dentro, :nth-child() siempre funciona con una fórmula an + b. n es un contador que el navegador va probando desde 0 hacia arriba (0, 1, 2, 3...), y por cada valor de n la fórmula devuelve la posición de un elemento a seleccionar. even, odd y 3n son simplemente casos particulares de esa misma fórmula.

/* even = 2n: selecciona las posiciones 2, 4, 6, 8...
   n=0 -> 2×0 = 0 (no existe la posición 0, se ignora)
   n=1 -> 2×1 = 2
   n=2 -> 2×2 = 4
   n=3 -> 2×3 = 6 */
tr:nth-child(even) { background: #f5f5f5; }
tr:nth-child(2n)    { background: #f5f5f5; } /* hace exactamente lo mismo */

/* odd = 2n + 1: selecciona las posiciones 1, 3, 5, 7...
   n=0 -> 2×0 + 1 = 1
   n=1 -> 2×1 + 1 = 3
   n=2 -> 2×2 + 1 = 5 */
tr:nth-child(odd)  { background: white; }
tr:nth-child(2n+1) { background: white; } /* hace exactamente lo mismo */

/* 3n selecciona una posición de cada tres: 3, 6, 9, 12...
   n=0 -> 3×0 = 0 (se ignora)
   n=1 -> 3×1 = 3
   n=2 -> 3×2 = 6 */
li:nth-child(3n) { color: #5b9bd5; }

/* 3n + 1 desplaza el patrón: en vez de 3, 6, 9, selecciona 1, 4, 7, 10... */
li:nth-child(3n + 1) { font-weight: 600; }

En la práctica, even/odd se usan casi siempre para "cebrar" una tabla, alternando el color de fondo de cada fila para que sea más fácil de leer sin tener que agregar clases manualmente. 3n aparece típicamente en grillas: si tenés tarjetas en una grilla de 3 columnas, nth-child(3n) selecciona la última tarjeta de cada fila, útil para sacarle el margen derecho y que no rompa el alineado.

:not() y :checked

/* Todos los botones excepto el que tiene la clase .btn--primary */
button:not(.btn--primary) {
  background: transparent;
}

/* El label que sigue justo a un checkbox marcado */
input:checked + label {
  color: green;
}

:not() invierte la selección, y :checked aplica a checkboxes o radios marcados — muy útil combinado con el combinador + para reaccionar a un estado sin JavaScript.

:is(), :where() y :has()

Son pseudo-clases más nuevas (CSS Selectors nivel 4) que agregan lógica al selector: agrupar alternativas, o mirar hacia adentro de un elemento en vez de solo hacia afuera. Al igual que :not(), se escriben con un solo : porque siguen siendo pseudo-clases, no pseudo-elementos.

/* Sin :is(), hay que repetir el descendiente para cada contenedor */
header a, main a, footer a {
  color: blue;
}

/* Con :is(), se agrupan las alternativas en un solo selector */
:is(header, main, footer) a {
  color: blue;
}

:is() toma una lista de selectores separados por coma y matchea si el elemento cumple con cualquiera de ellos, ahorrando repetición. La especificidad de :is(...) es la del selector más específico de la lista.

/* :where() hace lo mismo que :is(), pero su especificidad es siempre 0 */
:where(header, main, footer) a {
  color: blue;
}

/* por eso es fácil de sobrescribir después con un selector simple */
a {
  color: purple;
}

La diferencia entre :is() y :where() es solo la especificidad: :where() vale 0-0-0 sin importar lo que tenga adentro, así que cualquier otro selector normal lo puede pisar sin pelear con la especificidad. Es ideal para estilos base o de librerías, donde no querés imponerte sobre el CSS de quien lo use.

/* :has() selecciona un elemento según lo que tiene ADENTRO */
.card:has(img) {
  padding: 0;
}

/* también sirve para reaccionar a un descendiente con cierto estado */
label:has(input:checked) {
  font-weight: 600;
}

/* o para el caso contrario: tarjetas que NO tienen imagen */
.card:not(:has(img)) {
  padding: 1.5rem;
}

:has() es distinto a todo lo anterior: hasta su llegada, CSS solo podía seleccionar hacia abajo o hacia los costados (descendientes, hermanos), nunca hacia arriba. :has() permite justamente eso — estilar un elemento padre según lo que contiene, algo que antes exigía JavaScript.

A tener en cuenta: :first-child exige que el elemento sea literalmente el primer hijo de su padre, aunque sea de otro tipo. Si buscás "el primer <li> entre sus hermanos <li>", la pseudo-clase correcta es :first-of-type.
estadosnth-child:has()pseudo-clases