Posicionar con anchor: tooltips y menús con CSS anchor positioning
En esta lección
Un tooltip, un menú desplegable o un selector personalizado tienen algo en común: deben aparecer pegados a otro elemento (el botón que los abre) y no taparse con el borde de la ventana. Durante años esto se resolvía con position: absolute más JavaScript para calcular coordenadas. Hoy CSS tiene una herramienta propia: el anchor positioning (posicionamiento por ancla), que te deja decir “coloca este elemento debajo de aquel” directamente en la hoja de estilos.
En esta lección verás cómo funciona, cómo hacer un tooltip y un menú y, porque la compatibilidad aún no es total, cómo dejar una alternativa que funcione en todos los navegadores.
Cuidado: el anchor positioning está disponible en los navegadores basados en Chromium (Chrome y Edge) desde 2024 y en Safari en versiones recientes. En Firefox puede faltar o estar en desarrollo según la versión. Antes de usarlo, mira su estado en caniuse.com y programa siempre una alternativa, como la que verás más abajo.
La idea: ancla y elemento anclado
Hay dos piezas:
- El ancla (anchor): el elemento de referencia, por ejemplo un botón. Le das un nombre con
anchor-name. - El elemento anclado: el que quieres colocar (un tooltip, un menú). Debe estar posicionado con
position: absoluteofixed, y se enlaza al ancla conposition-anchor.
<button class="boton">Opciones</button>
<ul class="menu">
<li>Editar</li>
<li>Borrar</li>
</ul>
.boton {
anchor-name: --boton; /* nombre del ancla (empieza por dos guiones) */
}
.menu {
position: absolute;
position-anchor: --boton; /* se ancla al botón */
top: anchor(bottom); /* mi borde superior, en el borde inferior del botón */
left: anchor(left); /* mi borde izquierdo, en el izquierdo del botón */
}
El nombre del ancla es un identificador con dos guiones (--boton), igual que las variables CSS, pero no es una variable. Y lo importante: el menú no tiene que estar dentro ni al lado del botón en el HTML. Solo hace falta que sea absolute o fixed.
La función anchor()
anchor() devuelve una coordenada del ancla, y se usa donde irían top, right, bottom, left (o inset):
.menu {
top: anchor(bottom); /* borde inferior del ancla */
left: anchor(left); /* borde izquierdo del ancla */
right: anchor(right); /* también puedes anclar el lado derecho */
}
Valores de anchor(): top, right, bottom, left, center, start, end, un porcentaje… Por ejemplo, para colocar un elemento centrado sobre el ancla:
.globo {
position: absolute;
position-anchor: --boton;
bottom: anchor(top); /* mi parte inferior toca la parte superior del ancla */
left: anchor(center); /* mi izquierda, en el centro del ancla */
translate: -50% 0; /* y me desplazo la mitad de mi ancho */
}
Si hay varios anclas, puedes indicar cuál usar dentro de la función: top: anchor(--boton bottom). Y existe anchor-size(), que lee el tamaño del ancla, por ejemplo para que el menú tenga el mismo ancho que el botón:
.menu {
width: anchor-size(width);
min-width: anchor-size(width);
}
position-area: el atajo más cómodo
Con anchor() hay que decir qué lado toca a qué lado. Con position-area lo resumes con palabras. Imagina una rejilla de 3 x 3 casillas con el ancla en la casilla central; eliges en qué casilla va el elemento:
+-----------+-----------+-----------+
| top left | top | top right |
+-----------+-----------+-----------+
| left | (ancla) | right |
+-----------+-----------+-----------+
|bottom left| bottom |bottom right|
+-----------+-----------+-----------+
.tooltip {
position: fixed;
position-anchor: --boton;
position-area: top; /* encima del ancla, centrado */
}
.menu {
position: fixed;
position-anchor: --boton;
position-area: bottom span-right; /* debajo, alineado a la izquierda, y se extiende a la derecha */
}
top,bottom,left,righty las esquinas (top left…) colocan el elemento en esa casilla.- Con
span-*el elemento ocupa varias casillas:bottom span-rightempieza en el borde izquierdo del ancla y puede crecer hacia la derecha;bottom span-lefttermina en el borde derecho del ancla, ideal para un botón pegado a la derecha de la pantalla. - Existen también versiones lógicas (
block-end,inline-start…), que se adaptan a idiomas de derecha a izquierda, como en propiedades lógicas.
Cuando uses position-area, pon los márgenes que necesites para separar el elemento del ancla (margin: 6px 0 0). Y si el elemento tiene inset con valores, resetéalos a auto.
Que se mueva solo: position-try-fallbacks
Lo más valioso del anchor positioning es que el navegador reubica el elemento si se sale de la ventana. Un tooltip encima del botón, si el botón está pegado al borde superior, se taparía; con una alternativa, pasa abajo solo:
.tooltip {
position-area: top;
position-try-fallbacks: flip-block; /* si no cabe arriba, prueba abajo */
}
.menu {
position-area: bottom span-right;
position-try-fallbacks: flip-block, flip-inline; /* arriba, o hacia la izquierda */
}
| Valor | Qué prueba |
|---|---|
flip-block | Invierte el eje vertical (arriba por abajo) |
flip-inline | Invierte el eje horizontal (izquierda por derecha) |
flip-block flip-inline | Invierte los dos a la vez |
--mi-opcion | Una opción personalizada con @position-try |
Las opciones se prueban en orden, y el navegador se queda con la primera que cabe. También puedes definir una opción propia:
@position-try --a-la-derecha {
position-area: right;
margin: 0 0 0 6px;
}
.tooltip {
position-area: top;
position-try-fallbacks: flip-block, --a-la-derecha;
}
Otra propiedad útil es position-visibility: anchors-visible, que oculta el elemento anclado cuando el ancla sale de la vista al hacer scroll, para que no quede un tooltip flotando solo.
Ejemplo 1: un tooltip
<button class="btn" aria-describedby="tip">Grado 6a</button>
<span id="tip" role="tooltip" class="tip">Dificultad media-alta en escalada deportiva</span>
.btn { anchor-name: --ayuda; }
.tip {
position: fixed;
position-anchor: --ayuda;
position-area: top;
margin-bottom: 6px;
position-try-fallbacks: flip-block;
background: #111827;
color: #fff;
padding: 6px 10px;
border-radius: 6px;
visibility: hidden;
}
.btn:hover + .tip,
.btn:focus-visible + .tip { visibility: visible; }
Recuerda la accesibilidad: el botón se enlaza con el tooltip por aria-describedby, el tooltip se muestra también con el foco (no solo con el ratón) y nunca debe ser la única forma de acceder a una información importante. Más sobre esto en accesibilidad.
Ejemplo 2: un menú con popover
El anchor positioning y el atributo popover se llevan muy bien, porque el popover vive en la capa superior y no se corta por ningún overflow de sus padres. Si no conoces el atributo, mira primero Popover.
<button id="b-menu" popovertarget="menu">Opciones</button>
<ul id="menu" popover>
<li><a href="#">Editar ruta</a></li>
<li><a href="#">Compartir</a></li>
<li><a href="#">Borrar</a></li>
</ul>
#b-menu { anchor-name: --menu; }
#menu {
margin: 0; /* quita el margen automático del popover */
position-anchor: --menu;
position-area: bottom span-right;
margin-top: 4px;
position-try-fallbacks: flip-block, flip-inline;
}
Un popover ya tiene position: fixed por defecto, así que no hace falta añadirlo. Fíjate en que hay que quitarle el margin: auto que trae, porque lo centraría en su área.
Nota: algunos navegadores usan el botón de
popovertargetcomo ancla implícita del popover, sin tener que dar unanchor-name. Es cómodo, pero todavía no es universal: si quieres que sea predecible, declara el ancla explícitamente como en el ejemplo.
Varios botones, varios menús
Un anchor-name repetido es un problema: si dos botones se llaman --menu, el navegador elige el último de la página para todos. Hay dos soluciones. Una es dar un nombre único a cada pareja (style="anchor-name: --menu-1"). La otra es acotar el nombre a un contenedor con anchor-scope:
.fila {
anchor-scope: --menu; /* cada .fila tiene su propia ancla --menu */
}
.fila .boton { anchor-name: --menu; }
.fila .menu { position-anchor: --menu; }
La alternativa que funciona en todos los navegadores
Mientras la compatibilidad no sea total, escribe primero la versión clásica y mejora con @supports. Un navegador que no entienda position-area ignora el bloque y usa la alternativa.
Alternativa 1: tooltip con position: absolute
Envuelve botón y tooltip en un contenedor con position: relative; el tooltip se coloca con top: 100%:
.contenedor { position: relative; display: inline-block; }
.tip {
position: absolute;
top: 100%; /* justo debajo del botón */
left: 0;
margin-top: 6px;
width: max-content;
max-width: 220px;
}
/* Mejora donde esté disponible */
@supports (position-area: bottom) {
.btn { anchor-name: --ayuda; }
.tip {
position: fixed;
position-anchor: --ayuda;
inset: auto;
margin: 0 0 6px;
position-area: top;
position-try-fallbacks: flip-block;
}
}
Esta versión funciona en cualquier navegador, pero no se reajusta si el tooltip se sale de la pantalla. Es el compromiso. Para más sobre absolute y relative, mira Posicionamiento.
Alternativa 2: popover con JavaScript
Un popover está en la capa superior, y ahí position: absolute no se relaciona con tu contenedor relative: se coloca respecto a la ventana. Por eso, para un popover, la alternativa es calcular la posición del botón con getBoundingClientRect():
if (!CSS.supports('position-area: bottom')) {
const menu = document.querySelector('#menu');
const boton = document.querySelector('#b-menu');
menu.addEventListener('toggle', (e) => {
if (e.newState !== 'open') return;
const r = boton.getBoundingClientRect();
menu.style.margin = '0';
menu.style.inset = `${r.bottom + 4}px auto auto ${r.left}px`;
});
}
CSS.supports() pregunta al navegador si entiende una propiedad, así que el código solo se ejecuta donde hace falta. Más adelante, cuando todos los navegadores lo admitan, podrás borrar el bloque.
Cuándo usarlo
- Sí: tooltips, menús desplegables, selectores personalizados, avisos pegados a un campo, globos de ayuda. Todo lo que debe seguir a otro elemento.
- No: colocar bloques grandes de la página (para eso, Grid y Flexbox), ni modales centrados (para eso,
<dialog>; mira Centrar elementos).
Errores frecuentes
- Olvidar
position: absoluteofixed. Sin eso, el elemento ni se ancla ni se mueve. - No poner nombre al ancla o escribirlo distinto.
--botony--Botonson nombres diferentes, y los dos guiones iniciales son obligatorios. - Repetir
anchor-nameen una lista. Gana el último; da un nombre único a cada uno o usaanchor-scope. - No limpiar los
insetheredados al usarposition-area. Si el elemento tienetop: 0de otra regla, se mezcla; resetea coninset: auto. - Dejar el
margin: autodel popover. Lo centra en su área; ponlemargin: 0. - No dar una alternativa. En un navegador sin soporte, el elemento sale en la esquina o en cualquier sitio.
- Depender de que el tooltip sea la única fuente de información. Los usuarios táctiles y de lector de pantalla pueden no verlo.
Resumen
| Propiedad o función | Para qué sirve |
|---|---|
anchor-name: --x | Convierte un elemento en ancla con nombre |
position-anchor: --x | Enlaza el elemento posicionado con el ancla |
anchor(bottom), anchor(left)… | Lee una coordenada del ancla |
anchor-size(width) | Lee el tamaño del ancla |
position-area | Elige la casilla donde va (top, bottom span-right…) |
position-try-fallbacks | Alternativas si no cabe (flip-block, flip-inline) |
@position-try | Define una alternativa propia |
position-visibility | Oculta el elemento si el ancla no se ve |
anchor-scope | Acota un nombre de ancla a un contenedor |
@supports (position-area: bottom) | Mejora progresiva con alternativa |
En la siguiente lección verás cómo centrar de todas las formas posibles en Centrar elementos. Y si quieres volver a la base de la colocación, repasa Posicionamiento y Popover.
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.