Durante años, la respuesta a “¿cómo hago X en CSS?” solía ser “usa JavaScript”. En 2026, esa respuesta ya no aplica en la mayoría de los casos. Tres funcionalidades en particular han redibujado el mapa del diseño en el navegador.
Tabla de contenidos
Open Tabla de contenidos
Container Queries: responde al contenedor, no a la pantalla
Las media queries responden al ancho del viewport. El problema: un componente puede vivir en una columna estrecha o en una ancha dependiendo del diseño del padre. Las container queries resuelven esto.
/* Declarar el contenedor */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* El componente responde a su contenedor */
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
}
.card__image {
grid-row: 1 / 3;
}
}
@container card (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
}card.css
<!-- El mismo componente funciona en cualquier contexto -->
<aside class="card-wrapper" style="width: 300px">
<article class="card">...</article>
<!-- diseño vertical -->
</aside>
<main class="card-wrapper" style="width: 700px">
<article class="card">...</article>
<!-- diseño horizontal -->
</main>card.html
Unidades de container query
Las queries también exponen unidades relativas al contenedor:
.card__title {
font-size: clamp(1rem, 4cqi, 2rem); /* cqi = container query inline size */
}typography.css
La pseudoclase :has() — el selector de padre que siempre quisimos
:has() selecciona un elemento basándose en sus descendientes. Es el “selector de padre” que el CSS nos negó durante décadas.
/* Formulario con campo obligatorio vacío */
form:has(input:required:invalid) .submit-btn {
opacity: 0.5;
pointer-events: none;
}
/* Card que contiene una imagen: diseño diferente */
.card:has(img) {
display: grid;
grid-template-columns: 150px 1fr;
}
.card:not(:has(img)) {
padding: 1.5rem;
}
/* Navbar con menú abierto: deshabilitar scroll en el body */
body:has(.nav-menu[aria-expanded="true"]) {
overflow: hidden;
}styles.css
:has()tiene soporte en todos los navegadores modernos desde 2023. Puedes usarlo en producción hoy mismo sin polyfills.
Anchor Positioning: tooltips y popovers sin JavaScript
Antes de Anchor Positioning, colocar un tooltip relativo a su disparador requería calcular posiciones con JavaScript. Ya no:
/* Declarar el ancla (anchor) */
.btn-trigger {
anchor-name: --my-button;
}
/* Posicionar el tooltip relativo al ancla */
.tooltip {
position: absolute;
position-anchor: --my-button;
bottom: calc(anchor(top) + 8px);
left: anchor(center);
transform: translateX(-50%);
/* Girar automáticamente si no cabe */
position-try-fallbacks: flip-block;
}tooltip.css
<button class="btn-trigger" popovertarget="tip">Pasa el ratón</button>
<div id="tip" class="tooltip" popover>
Este tooltip se posiciona solo, sin JS.
</div>tooltip.html
position-try-fallbacks: lógica de colisiones declarativa
.tooltip {
position-try-fallbacks:
flip-block,
/* intenta arriba si no cabe abajo */ flip-inline,
/* intenta izquierda si no cabe derecha */ flip-start; /* combina ambos */
}tooltip.css
¿Y el soporte?
| Funcionalidad | Chrome | Firefox | Safari |
|---|---|---|---|
| Container Queries | 105+ ✓ | 110+ ✓ | 16+ ✓ |
:has() | 105+ ✓ | 121+ ✓ | 15.4+ ✓ |
| Anchor Positioning | 125+ ✓ | 131+ ✓ | 18+ ✓ |
En 2026, con la distribución actual de navegadores, puedes usar las tres en producción para la mayoría de proyectos. Considera usar polyfills solo si tu audiencia incluye navegadores muy antiguos.
El CSS de hoy es declarativo y expresivo
La revolución silenciosa del CSS no fue Grid ni Flexbox. Fue el cambio de mentalidad: el navegador razona sobre restricciones, tú declaras el resultado deseado. Las container queries, :has() y el anchor positioning son la culminación de ese paradigma.
Traducido del original (Andrés Ujpán)