Cómo y cuándo utilizar :has()

Tabla de contenidos

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

El genio de Aladdin ennumerando sus ocho dedos

: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.

Recursos

comments powered by Disqus

Si te ha parecido interesante

Tanto si tienes alguna duda o quieres charlar sobre este tema, como si el contenido o nuestros perfiles te parecen interesantes y crees que pdemos hacer algo juntos, no dudes en ponerte en contacto con nosotros a través de twitter o en el email hola@mamutlove.com