CSS anchor positioning y Popover API: tooltips y menús sin JavaScript
Posicionar un tooltip o un menú junto a su botón ya no exige medir coordenadas en JavaScript: con CSS anchor positioning el navegador coloca y voltea el flotante, y con la Popover API lo muestra y lo cierra. Aquí está el CSS completo, con los fallbacks y los límites.
El problema que arrastramos desde siempre
Medir con getBoundingClientRect y recalcular en cada scroll
Un menú de acciones en una tabla, el botón de ayuda de un panel de administración, el tooltip de una barra de herramientas: todos los flotantes que se cuelgan de otro elemento se construían igual. Primero se calcula la posición del disparador con getBoundingClientRect(), después se escriben left y top en el flotante, y luego hay que repetir el cálculo en cada scroll y cada cambio de tamaño de la ventana. Si el flotante es más ancho que el hueco que queda en pantalla, el ajuste se hace a mano.
El z-index a mano, el cierre al hacer clic fuera y el menú que corta el overflow
A eso se suman tres problemas pequeños que se repiten en cada proyecto: si un contenedor tiene overflow: hidden, el menú aparece cortado; si hay capas apiladas, se pelea con z-index hasta que algo queda por encima; y el cierre al hacer clic fuera es un listener más que hay que añadir y quitar sin dejarse ninguno. Nada de esto es lógica de tu producto: es fontanería de plataforma.
Lo que el navegador ya hace por ti en 2026
Dos piezas de plataforma cubren la mayor parte de esa fontanería y no se solapan. La Popover API se encarga de mostrar, ocultar, gestionar el foco y promover el flotante a la capa superior del documento. CSS anchor positioning se encarga de dónde aparece respecto a su disparador y de qué hacer cuando no cabe. En MDN, la Popover API figura como Baseline 2025 y las propiedades de anclaje —position-anchor, position-area, position-try-fallbacks, position-visibility, anchor-scope— como Baseline 2026. Que sea Baseline significa que los motores principales lo implementan en sus versiones actuales; aun así, conviene comprobar la tabla de compatibilidad antes de apoyar en una de ellas un componente crítico.
Lee también
Paso 1: mostrar y ocultar con la Popover API
popover y popovertarget: un desplegable sin una línea de JavaScript
<button type="button" popovertarget="menu-acciones">Acciones</button>
<div id="menu-acciones" popover>
<a href="/panel/editar">Editar</a>
<a href="/panel/duplicar">Duplicar</a>
<a href="/panel/borrar">Borrar</a>
</div>El atributo popover convierte el elemento en un flotante no modal y popovertarget apunta a su id: el navegador alterna entre visible y oculto sin que escribas un solo addEventListener. El flotante pasa a la capa superior, así que el z-index de sus vecinos deja de importar, y su estado visible se consulta con la pseudoclase :popover-open.
Cierre ligero, foco y capa superior
El popover se cierra al hacer clic fuera y al pulsar Escape, el navegador gestiona el orden de apilado y devuelve el foco al elemento correcto al cerrarse. Esa es exactamente la parte que casi nadie implementa bien a mano.
Los modos: auto, manual y hint
El atributo admite valores con comportamientos distintos. popover="auto" es el caso descrito: se cierra con el clic fuera y cierra a los otros popovers automáticos que estén abiertos. popover="manual" no se cierra solo, y se usa cuando el panel no debe desaparecer porque el usuario haga clic en otra parte. popover="hint" está pensado para ayudas que no deben llevarse el foco ni interrumpir el flujo, como el tooltip de una barra de herramientas.
Paso 2: anclar el flotante a su disparador
anchor-name en el disparador y position-anchor en el flotante
.disparador {
anchor-name: --boton-acciones;
}
.menu {
position: absolute;
position-anchor: --boton-acciones;
position-area: bottom span-right;
}El ancla se declara con anchor-name en el elemento de referencia y se consume con position-anchor en el flotante. El nombre empieza por dos guiones, como una variable de CSS, porque es exactamente eso: un identificador. Si te suena de tutoriales antiguos, position-area es el nombre vigente de lo que antes se llamaba inset-area.
position-area: arriba, abajo y a los lados sin cuentas
position-area divide el espacio alrededor del ancla en una cuadrícula de nueve celdas y acepta una o dos palabras clave. bottom equivale a bottom span-all; bottom span-right coloca el flotante debajo y lo extiende hacia la derecha; top span-left hace lo simétrico por arriba. Con eso se cubre la gran mayoría de los casos sin escribir una sola coordenada.
Ajuste fino con anchor() y tamaño con anchor-size()
Cuando hay que salirse de la cuadrícula, la función anchor() devuelve la posición de un borde del ancla y se usa en las propiedades de inset: top: anchor(bottom) alinea el borde superior del flotante con el inferior del disparador. anchor-size() hace lo propio con el tamaño, por ejemplo width: anchor-size(width) para que un menú mida exactamente lo que su botón.
Paso 3: que nunca se salga de la pantalla
position-try-fallbacks: flip-block y flip-inline
.menu {
position-try-fallbacks: flip-block, flip-inline;
}El navegador prueba las posiciones de la lista en orden y se queda con la primera en la que el flotante cabe. flip-block invierte el eje principal —arriba por abajo— y flip-inline el transversal, izquierda por derecha. Es la misma lógica que las librerías de colisiones, resuelta dentro del motor de render.
Un fallback a medida con @position-try
@position-try --esquina-superior {
position-area: top span-right;
margin-block-end: 0.5rem;
}
.menu {
position-area: bottom span-right;
position-try-fallbacks: --esquina-superior, flip-block;
}La regla @position-try define una alternativa con nombre, y position-try-fallbacks la usa como cualquier otra palabra clave. No conviene confundir los tres nombres parecidos: @position-try es la regla, position-try-fallbacks es la propiedad con la lista y position-try es la abreviatura de orden.
Ocultar el flotante cuando su ancla desaparece
Si el ancla se va de la vista al hacer scroll, el flotante puede quedarse colgado en mitad de la pantalla. position-visibility: anchors-visible lo oculta cuando su ancla ya no se ve. El valor por defecto es no ocultarlo nunca, que suele ser el comportamiento equivocado.
Paso 4: detalles que se olvidan (y rompen el componente)
anchor-scope: varios pares que comparten nombre en la misma lista
En una lista con veinte filas, cada fila tiene su botón y su menú. Si todos comparten el mismo anchor-name, el navegador tiene que resolver a cuál se refiere cada position-anchor. anchor-scope limita el alcance de un nombre de ancla a un subárbol, así que el nombre se resuelve dentro de la fila correcta.
Animación de entrada con @starting-style y transition-behavior: allow-discrete
.menu {
opacity: 0;
transform: translateY(-0.25rem);
transition: opacity 150ms, transform 150ms, display 150ms allow-discrete;
}
.menu:popover-open {
opacity: 1;
transform: translateY(0);
}
@starting-style {
.menu:popover-open {
opacity: 0;
transform: translateY(-0.25rem);
}
}Un popover pasa de display: none a visible, y una transición no arranca desde un estado que el navegador nunca pintó: eso lo resuelve @starting-style. La palabra clave allow-discrete permite además animar propiedades discretas como display y overlay, que es lo que evita que el panel desaparezca de golpe al cerrarse. Es la misma técnica que usamos al animar cambios de página con la View Transitions API.
La estructura HTML mínima que evita problemas
Disparador y panel como hermanos, ambos dentro del mismo contenedor de la fila o de la tarjeta, y el panel con position: absolute. Evita meter el flotante dentro de otro elemento con overflow: hidden: aunque la capa superior lo dibuje por encima, el recorte del contenedor sigue afectando a lo que hay dentro.
Cuándo todavía necesitas JavaScript
Ancla que cambia según el ítem: el caso del tooltip compartido
El caso típico es un tooltip único que se mueve por una lista larga: hay un solo elemento flotante y el ancla debe cambiar según la fila sobre la que está el ratón. Ahí hay que actualizar position-anchor por JavaScript, porque el CSS no adivina qué ítem está activo.
Navegación con flechas y roles de menú
El navegador gestiona el foco del popover, pero no convierte tu lista de enlaces en un menú navegable con las flechas. Si el componente es un menú, añade role="menu" al panel y role="menuitem" a cada opción, y mantén la navegación por teclado. Sustituir un desplegable accesible por uno inaccesible no es un buen cambio.
Lo que sí puedes borrar hoy
El cálculo de coordenadas, la pelea con el z-index, el cierre al hacer clic fuera y el reposicionamiento cuando el flotante se sale de pantalla desaparecen donde la API esté soportada. Eso es menos JavaScript propio y, en muchos casos, una dependencia menos. Si trabajas con utilidades en lugar de CSS plano, mira qué trae tu versión de Tailwind antes de dar por hecho que necesita valores arbitrarios: lo que publicamos sobre Tailwind CSS 4.3 sirve de referencia.
Compatibilidad y red de seguridad
Qué significa que sea Baseline y cómo verificarlo
Baseline no es un sello decorativo: significa que la función es interoperable entre los motores principales. Las propiedades de anclaje están en Baseline 2026 y la Popover API en Baseline 2025, según MDN, pero conviene confirmar la tabla de compatibilidad el día que escribes el componente, especialmente si tu audiencia usa navegadores que se actualizan despacio o si dependes de @position-try.
Detección con @supports, no con el user-agent
@supports (anchor-name: --x) {
.disparador { anchor-name: --boton-acciones; }
.menu { position-anchor: --boton-acciones; position-area: bottom span-right; }
}La pregunta correcta es por la capacidad que necesitas, no por la marca del navegador. El CSS no soportado se ignora sin errores, así que la red de seguridad es dejar un respaldo que funcione sin la API.
El respaldo para navegadores antiguos
Con un contenedor con position: relative, el flotante con position: absolute y un par de valores de top y left, tienes un menú decente allí donde la API no llegue. No es la misma experiencia, pero el contenido sigue siendo usable y el componente no se rompe.
Checklist antes de publicarlo
Teclado, foco y lector de pantalla
Abre y cierra el flotante solo con el teclado, comprueba que el foco entra y sale donde debe, que Escape lo cierra y que el lector de pantalla anuncia el panel y sus opciones. Si el componente es un menú, la lista de comprobaciones de la auditoría de accesibilidad WCAG 2.2 AA te sirve tal cual.
Errores comunes: sin fallback, nombres repetidos y ancla fuera de alcance
Los tres fallos que más se repiten: olvidar el respaldo para navegadores sin soporte; repetir el mismo nombre de ancla en varios pares sin acotarlo con anchor-scope; y anclar a un elemento fuera del alcance del flotante —el ancla tiene que ser visible y compartir contexto, no vivir en otra rama del DOM escondida con display: none—.
Conclusión
CSS anchor positioning y la Popover API no eliminan todas las librerías, pero sí se llevan por delante el trabajo más tedioso: colocar, voltear, apilar y cerrar. En el caso típico —un menú que cuelga de un botón y un tooltip que no debe salirse de la pantalla— el componente queda con CSS y atributos HTML, y el JavaScript se reserva para lo que la plataforma todavía no resuelve. Empieza por el componente más simple que tengas, mide cuánto código te ahorras y decide después; y si quieres seguir afinando el front-end sin dependencias, en el blog tenemos más tutoriales de CSS con casos concretos.


