Históricamente, para aplicar diferentes diseños a un componente en función de su contenido, se requería JavaScript o lógica condicional en el lenguaje de plantillas HTML para insertar clases modificadoras (como .card--has-image o .card--text-only).
La pseudoclase :has() elimina esta necesidad, ya que actúa como un selector superior. Te permite aplicar diseño de forma condicional a un elemento contenedor en función de la presencia o ausencia de elementos descendientes específicos.
Con :has(), puedes definir fácilmente distintas variaciones de diseño por completo en CSS según el contenido real del DOM de un componente. También puedes combinarlo de forma opcional con :not() para segmentar de forma explícita la ausencia de contenido y definir diseños predeterminados.
Implementa el diseño de contenedores basado en el contenido
OBLIGATORIO: Debes usar el selector :has() en el elemento contenedor para detectar la presencia de contenido secundario específico.
Para compilar un componente que cambie su diseño según su contenido, haz lo siguiente:
- Define el diseño predeterminado: Aplica los estilos de diseño base al elemento contenedor (p.ej., una pila simple de una sola columna).
- Aplica anulaciones basadas en el contenido: Segmenta el contenedor con
:has([child-selector])y aplica los nuevos estilos de diseño para cuando ese contenido esté presente (p.ej., una cuadrícula de varias columnas).
Ejemplo: Un componente de tarjeta que cambia a un diseño en paralelo si hay una imagen presente.
/* 1. Define the default state on the component container */
/* This applies when there is NO image */
.article-card {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.5rem;
border: 1px solid #ccc;
border-radius: 8px;
}
/* 2. Apply styles when a specific child element is present */
/* MANDATORY: Target the container and use :has() to check for the descendant */
.article-card:has(img) {
/* Change the layout to a row if an image exists */
flex-direction: row;
align-items: center;
}
/* Optional: Style the image itself (no :has() needed here) */
.article-card img {
width: 150px;
height: auto;
border-radius: 4px;
}
/* You can also combine :has() and :not() to explicitly target the ABSENCE of content */
/* This selects a card that DOES NOT have an image */
.article-card:not(:has(img)) {
/* E.g., apply a different background color for text-only cards */
background-color: #f9f9f9;
}
<!-- Assume an <h1> precedes these components in the document layout -->
<!-- A card WITH an image (will use row layout) -->
<article class="article-card">
<img src="thumbnail.jpg" alt="Article thumbnail" />
<div class="content">
<h2>Card With Image</h2>
<p>This card lays out its content horizontally.</p>
</div>
</article>
<!-- A card WITHOUT an image (will use default column layout) -->
<article class="article-card">
<div class="content">
<h2>Text-Only Card</h2>
<p>This card lays out its content vertically, and gets its background color from the :not(:has()) rule.</p>
</div>
</article>
Sugerencia de rendimiento: Cuando uses :has(), limita el selector al contenedor de componentes más específico posible (como .article-card). Evita fijarlo a elementos de nivel muy alto, como body:has(img), si los cambios de diseño están muy localizados, ya que las consultas :has() amplias pueden activar más cálculos de diseño.
Estrategias de resguardo
Si el diseño basado en el contenido es fundamental para la experiencia del usuario o el diseño de la página, debes proporcionar un resguardo para los navegadores que no admiten el selector :has(). Para efectos puramente decorativos, se puede usar :has() como una mejora progresiva sin un resguardo.
OBLIGATORIO: Cuando implementes un resguardo para los diseños críticos, debes usar @supports not selector(:has(*)) en tu CSS para definir un resguardo tradicional basado en clases (p.ej., .has-image).
A diferencia del diseño interactivo basado en el estado, la presencia del contenido suele conocerse en el momento de la renderización. La alternativa más sólida es que tu motor de plantillas del servidor (o generador de sitios estáticos) inserte una clase como .has-image en el contenedor si el elemento secundario (como una imagen) existe en los datos.
Si la renderización del servidor no es una opción, debes usar una secuencia de comandos pequeña con CSS.supports() para detectar el contenido y agregar la clase durante la carga o después de la inserción de contenido dinámico.
/* Fallback CSS for older browsers */
/* We check if the browser DOES NOT support the :has() selector */
@supports not selector(:has(*)) {
/* Define a traditional modifier class that applies the exact same layout overrides */
.article-card.has-image {
flex-direction: row;
align-items: center;
}
.article-card:not(.has-image) {
background-color: #f9f9f9;
}
}
/* Fallback JavaScript for older browsers (if not using SSR to add the class) */
/* Check for support before running the script to avoid unnecessary work in modern browsers */
if (!CSS.supports('selector(:has(*))')) {
// Find all components that need checking
const cards = document.querySelectorAll('.article-card');
cards.forEach(card => {
// If the critical content exists, manually add the fallback class
if (card.querySelector('img')) {
card.classList.add('has-image');
}
});
}