El año 2023 debía de ser el año de :has() una función de CSS que permite
evaluar si un selector “contiene algún otro elemento”, útil para aplicar estilos
a un contenedor en función de su contenido, algo parecido al selector padre que
nunca llegó a CSS (ni llegará), pero tuvimos que esperar hasta final de año para
poder usarlo a discrección, y es que fue entonces cuando Firefox lo implementó y
quedaba por fin ampliamente cubierto por la gran mayoría de navegadores.
CanIUse
Hasta ese momento podíamos usarlo o bien para detalles estéticos que no fueran críticos y asumir que habría usuarios que no lo verían, o bien ofrecer una alternativa (fallback) usando javascript, lo que suponía añadir complejidad al asunto (a menudo no necesaria) aumentando el riesgo de fallar, porque codificar nos empuja a la posibilidad de cometer errores, ¿no?
Así que este artículo llega con un año de retraso pero al mismo tiempo llega más maduro.
Casos de uso
:has() acepta como argumento una
lista de selectores relativos
(<relative-selector-list>) y comprueba si al menos uno de ellos coincide con
el DOM. Si la condición se cumple.
El ejemplo sencillo sería p:has(> a) en el que se evalúa si un párrafo
contiene un enlace. La diferencia con p > a es que en el primer caso estamos
seleccionando el p mientras que en el segundo los estilos se aplican a a que
es el elemento seleccionado. Por eso decimos que podemos aplicar estilos
condicionalmente en función de su contenido o que es como un selector padre.
Pero es algo más.
Como el selector es relativo, el argumento hace relación al elemento que evalúa
(el que lo usa), pero podríamos querer aplicar estilos a un elemento hermano en
función de si el anterior (o padre) tiene alguna particularidad, por ejemplo:
section:has(.box) + section
A partir de aquí, puedes intentar las queries más raras que se te ocurran,
algunas funcionarán y otras no o no a la primera, sobre todo son difíciles las
que involucran todas las pseudo-clases del estilos nth-of-type() pero algunas
de las más comunes son, por ejemplo:
Comprobar si un elemento contiene otro
Evaluar si un elemento contiene otro elemento, clase u atributo.
article:has(h1, h2, h3, h4, h5, h6) {}
section:has([class^="item"]) li[class] {}
Comprobar si un elemento NO contiene otro
Podemos también evaluar si un elemento no contiene otro elemento, clase u atributo.
figure:not(figcaption) {}
Hay que prestar atención al combinarlo con :not() porque el orden de
declaración altera el sentido de la expresión, no es lo mismo
li:not(:has(span)) que li:has(:not(span)) porque no es lo mismo que no
contenga span a que contengan cualquier cosa que no sea un span.
Contar hijos
Podemos evaluar el número de hijos que tiene un nodo para aplicar una solución
tipo flex o grid.
ul {
display: flex;
}
ul:has(> :nth-child(5)) {
display: grid;
grid-template-columns: 1fr 1fr;
}
Comprobar si hay elementos abiertos
Como podemos evaluar clases o atributos, es una solución ideal para bloquear el scroll de la página cuando un elemento emergente está abierto.
body:has(.is-modal-open) {
overflow-y: hidden;
}
body:has(dialog[open], details[open]) {
overflow-y: hidden;
}
body:has([aria-expanded="true"]) {
overflow-y: hidden;
}
Peculiaridades
Por supuesto no todo es jauja, hay ciertas limitaciones

:has() no se puede anidar, así que selectores de este estilo no funcionarán
body:has(section:has(span)) {
outline: 10px solid lime;
outline-offset: -12px;
}
:has() no puede evaluar
pseudo-elementos
(esos elementos que son virtuales) porque no los puede evaluar en el DOM.
body:has(::before) {
color: #f06;
}
Demo
Aquí una pequeña demo para que puedas probar lo explicado anteriormente.
Para ampliar más
Las combinaciones de :has() son muchas, tantas que rara vez las necesitarás
usar porque podrás poner solución con otras herramientas o enfoque, pero
conviente saber hasta dónde se puede llegar a estresar. Para ampliar más, no se
me ocurre mejor lugar bram.us donde
encontrarás una amplia colección de artículos relacionados, en los que se lleva
la capacidad de :has() muy al límite.
