Saltar al contenido
anclaje.css · devschool

Posicionar con anchor: tooltips y menús con CSS anchor positioning

Lección 16 de 50 · 10 min de lectura · Actualizado el

En esta lección
  1. La idea: ancla y elemento anclado
  2. La función anchor()
  3. position-area: el atajo más cómodo
  4. Que se mueva solo: position-try-fallbacks
  5. Ejemplo 1: un tooltip
  6. Ejemplo 2: un menú con popover
  7. La alternativa que funciona en todos los navegadores
  8. Cuándo usarlo
  9. Errores frecuentes
  10. Resumen

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: absolute o fixed, y se enlaza al ancla con position-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, right y las esquinas (top left…) colocan el elemento en esa casilla.
  • Con span-* el elemento ocupa varias casillas: bottom span-right empieza en el borde izquierdo del ancla y puede crecer hacia la derecha; bottom span-left termina 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 */
}
ValorQué prueba
flip-blockInvierte el eje vertical (arriba por abajo)
flip-inlineInvierte el eje horizontal (izquierda por derecha)
flip-block flip-inlineInvierte los dos a la vez
--mi-opcionUna 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 popovertarget como ancla implícita del popover, sin tener que dar un anchor-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: absolute o fixed. Sin eso, el elemento ni se ancla ni se mueve.
  • No poner nombre al ancla o escribirlo distinto. --boton y --Boton son nombres diferentes, y los dos guiones iniciales son obligatorios.
  • Repetir anchor-name en una lista. Gana el último; da un nombre único a cada uno o usa anchor-scope.
  • No limpiar los inset heredados al usar position-area. Si el elemento tiene top: 0 de otra regla, se mezcla; resetea con inset: auto.
  • Dejar el margin: auto del popover. Lo centra en su área; ponle margin: 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ónPara qué sirve
anchor-name: --xConvierte un elemento en ancla con nombre
position-anchor: --xEnlaza 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-areaElige la casilla donde va (top, bottom span-right…)
position-try-fallbacksAlternativas si no cabe (flip-block, flip-inline)
@position-tryDefine una alternativa propia
position-visibilityOculta el elemento si el ancla no se ve
anchor-scopeAcota 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).

anclaje.html
Resultado

Pon a prueba lo que has aprendido

[CSS] ¿Qué hay que hacer para que un tooltip se ancle a un botón?

[CSS] ¿Dónde se coloca el elemento con esta declaración?
position-area: bottom span-right;

[CSS] Un tooltip está encima del botón y, si el botón está pegado al borde superior, se sale de la ventana. ¿Qué propiedad lo mueve abajo automáticamente?

[CSS] Un menú es un popover. En un navegador sin anchor positioning, ¿qué alternativa es adecuada?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.