CSS Anchor Position: guía práctica y patrones
Durante años, position: absolute/relative/fixed/sticky ha sido una fuente constante de dudas: ¿por qué absolute necesita un contenedor con relative?, ¿por qué mi tooltip “salta” fuera del viewport?, ¿cómo anclo algo a otro elemento… si no es su contenedor? En mis clases lo veía a diario: sin JavaScript, construir un popup o un menú contextual era casi misión imposible, y en WordPress el problema crecía por la profundidad del HTML de muchas plantillas, que dificulta identificar el contenedor real y los stacking contexts.
El nuevo CSS Anchor Positioning cambia la conversación: puedes nombrar un elemento como ancla y posicionar otro respecto a él, sin exigir relación de contenedor. Resultado: menos JS, menos hacks y una carga mental más baja para el equipo. En esta guía verás cuándo usarlo, cómo aplicarlo paso a paso, patrones productivos (tooltips, popovers, menús) y anti-patrones que te ahorrarán horas de depuración, con foco especial en WordPress.
Qué es CSS Anchor Positioning y por qué importa
CSS Anchor Positioning permite anclar el posicionamiento de un elemento a otro elemento arbitrario, usando:
anchor-name: nombra el elemento ancla.position-anchor: indica a qué ancla se adhiere el elemento posicionado.- Funciones como
anchor()yanchor-size()para leer posiciones y tamaños del ancla. - Reglas como
@position-tryy propiedadesposition-try,position-try-order,position-visibilitypara recolocar automáticamente el elemento si la posición preferida no cabe (p. ej., inversión arriba/abajo).
Referencia oficial: MDN – CSS anchor positioning y
anchor().
Ventajas clave:
- Menos dependencia de JS para UI flotante (tooltips/popovers).
- Independencia del contenedor: el ancla puede estar “lejos” en el DOM.
- Patrones declarativos: reposicionamiento automático con try tactics.
- Mejor DX (Developer Experience) y menor layout thrashing cuando el sistema recoloca.
Consideración de soporte: hoy su disponibilidad aún es limitada y conviene aplicarlo con progressive enhancement (ver patrón más abajo).
Antes vs. ahora: del position clásico al anclaje moderno
El problema clásico
Con absolute, el sistema calcula posiciones respecto al contenedor posicionado (el primer ancestro con position distinto de static). Si tu tooltip depende de un botón que no es ese contenedor, llega el festival de wrappers, transform que crean nuevos contextos, o event listeners para calcular offsets con JS. En themes de WordPress, donde el HTML es profundo, entender quién es el contenedor real se convierte en una arqueología DOM.
El giro de Anchor Positioning
Ahora nombras el botón como ancla y pegas el tooltip al botón, no al contenedor. No necesitas reestructurar el DOM ni añadir position: relative en una cadena de contenedores.
Primer ejemplo: tooltip mínimo con anchor() (con fallback)
HTML
<button class="btn" aria-describedby="t1">Comprar</button> <div id="t1" role="tooltip" class="tooltip">Añadir al carrito</div>
CSS (progressive enhancement)
/* 1) Estilos base (funciona en todos los navegadores) */
.btn { inline-size: auto; }
.tooltip {
position: absolute;
/* Fallback clásico: debajo del botón con JS ligero o posición aproximada */
opacity: 0;
pointer-events: none;
transform: translateY(0.5rem);
}
/* 2) Activación por enfoque/hover (accesible) */
.btn:focus + .tooltip,
.btn:hover + .tooltip { opacity: 1; }
/* 3) Anchor Positioning si hay soporte */
@supports (position-anchor: --x) {
/* Nombramos el ancla en el TRIGGER */
.btn { anchor-name: --trigger; }
/* El tooltip se ancla al trigger */
.tooltip {
position-anchor: --trigger;
/* Colocar bajo el ancla, centrado horizontal */
top: anchor(bottom);
left: anchor(center);
transform: translate(-50%, 0.5rem);
}
}
Qué pasa aquí: el botón define anchor-name: --trigger. El tooltip usa position-anchor: --trigger y consulta anchor(bottom/center) para alinearse bajo y centrado respecto al botón. Si el navegador no soporta la feature, el bloque @supports no aplica y te quedas con un fallback (p. ej., un pequeño script que posicione el tooltip o una colocación aproximada).
Reposicionamiento automático: @position-try y position-try
Cuando el tooltip no cabe debajo, la UI debe recolocarse arriba. Con Anchor Positioning lo declaras:
@supports (position-anchor: --x) {
/* Definimos estrategias nombradas */
@position-try --below {
/* "Área" inferior del ancla */
inset-area: bottom;
}
@position-try --above {
inset-area: top;
}
.btn { anchor-name: --trigger; }
.tooltip {
position-anchor: --trigger;
/* Estrategia preferida y alternativas */
position-try: --below, --above;
/* Ajustes finos apoyados en anchor() */
left: anchor(center);
transform: translate(-50%, 0.5rem);
}
}
Con esto, el navegador intenta colocar el tooltip debajo; si no hay espacio, prueba arriba automáticamente. Puedes añadir más variantes (izquierda/derecha) y ordenar intentos con position-try-order.
Ejemplo 2: popover alineado al borde con anchor-size()
Para menús que deben igualar el ancho del botón:
@supports (position-anchor: --x) {
.btn { anchor-name: --menu-trigger; }
.menu {
position: absolute;
position-anchor: --menu-trigger;
/* Alinear borde izquierdo y copiar el ancho del trigger */
left: anchor(left);
width: anchor-size(width);
top: anchor(bottom);
transform: translateY(0.25rem);
}
}
anchor-size(width) toma el ancho del ancla y lo aplica al menú, evitando cálculos JS y reflows manuales.
Anti-patrones frecuentes (y cómo evitarlos)
-
Olvidar el progressive enhancement
- Envuélvelo con
@supports (position-anchor: --x)y no rompas la UI básica.
- Envuélvelo con
-
Confiar en transformaciones del ancla
transformen el ancla puede crear nuevos contextos; comprueba que la medición no se vuelva contra ti.
-
Ignorar accesibilidad
- Tooltips/popovers necesitan roles ARIA, gestión de foco y escapado con
Esc. El posicionamiento no soluciona A11y por sí solo.
- Tooltips/popovers necesitan roles ARIA, gestión de foco y escapado con
-
Profundidad DOM en WordPress
- No “pelees” con el contenedor: nombra el ancla en el componente trigger (botón, icono del menú) y posiciona desde cualquier parte del árbol.
-
No probar overflows reales
- Usa
@position-trycon variantes para pantallas pequeñas y contenedores con scroll.
- Usa
WordPress en producción: truco para themes con HTML profundo
En block themes, el botón puede estar dentro de varios wrappers. No pasa nada: el ancla sigue siendo el botón.
<!-- Bloque de Botón -->
<div class="wp-block-buttons">
<div class="wp-block-button is-style-fill">
<a class="wp-element-button btn--cta">Comprar</a>
</div>
</div>
<!-- En otra zona del template -->
<div class="popover" id="buy-popover">Producto añadido ✓</div>
/* 1) Etiqueta el trigger como ancla */
.btn--cta { anchor-name: --buy; }
/* 2) El popover puede vivir en otro wrapper: no importa */
@supports (position-anchor: --x) {
.popover {
position: fixed; /* o absolute: depende de tu diseño */
position-anchor: --buy; /* se ancla al botón */
top: anchor(bottom);
right: anchor(right); /* alineado al borde derecho del botón */
transform: translateY(0.5rem);
}
}
Claves:
- Puedes usar
position: fixedsi el patrón lo requiere (HUD flotante, barra fija) manteniendo el anclaje semántico al botón real. - En Gutenberg, añade la clase
btn--ctadesde el panel avanzado del bloque. - Para diseño responsivo, combina
position-trycon media queries.
Accesibilidad mínima y JS opcional (solo para mejorar)
- Roles y relaciones:
aria-describedby(tooltip) oaria-controls/aria-expanded(popover). - Foco y teclado: abre con
Enter/Espacio, cierra con Esc, devuelve el foco al trigger. - JS Light: usa JS solo para gestión de estado; deja el posicionamiento a CSS. Un patrón típico:
const trigger = document.querySelector('.btn');
const tooltip = document.getElementById('t1');
trigger.addEventListener('focus', () => tooltip.dataset.open = 'true');
trigger.addEventListener('blur', () => tooltip.dataset.open = 'false');
trigger.addEventListener('mouseenter', () => tooltip.dataset.open = 'true');
trigger.addEventListener('mouseleave', () => tooltip.dataset.open = 'false');
.tooltip { opacity: 0; }
.tooltip[data-open="true"] { opacity: 1; }
Checklist de implementación rápida
- Definir
anchor-nameen el elemento trigger. - Aplicar
position-anchoren el flotante. - Posicionar con
anchor()y, si procede, dimensionar conanchor-size(). - Añadir
@position-trypara recolocar en caso de overflow. - Encapsular en
@supportsy dejar un fallback funcional. - Revisar A11y (roles, foco, teclado) y z-index/stacking.
- Probar en plantillas profundas (WordPress) y en móviles.
CSS Anchor Position trae un enfoque declarativo y robusto para UI flotante: anclas semánticas, menos JS, y recolocación inteligente. En escenarios reales —sobre todo en WordPress con HTML profundo— reduce el acoplamiento al contenedor y simplifica la experiencia de desarrollo. Si vienes del “mundo absolute/relative”, pensar en anclar a un elemento (y no a un contenedor) te abrirá patrones más limpios y mantenibles. Empieza por tooltips y popovers, añade @position-try y verás cómo la UI deja de luchar contra el layout.
Si te interesa llevar estos conceptos al siguiente nivel y aprender a crear interfaces dinámicas con HTML, CSS y JavaScript desde cero, inscríbete en el Curso de Programación Web de Escuela Espai. Es una formación completa donde practicarás posicionamiento, animaciones, responsive design y las bases del desarrollo front-end moderno.
