Sustituir las bibliotecas de tooltips por la API Popover y el posicionamiento con anclajes en CSS
Por qué se necesitan las herramientas de ayuda: Popper y la interfaz flotante, características nativas que ahora resuelven problemas de apilamiento, posicionamiento y cierre, y cuándo aún vale la pena usar una biblioteca de JavaScript.
Mostrar una línea de texto junto a un botón parece algo sencillo, pero existen Popper.js, Floating UI y numerosos paquetes auxiliares precisamente porque eso nunca fue posible. Una herramienta de ayuda es en realidad tres problemas separados superpuestos, y hasta hace poco la plataforma no ofrecía una solución adecuada para ninguno de ellos. Esta guía descompone estos problemas, muestra cuánto espacio ocupa cada uno en el JavaScript final y los relaciona con las dos funcionalidades del navegador que ahora abordan los casos más comunes: la API Popover y el posicionamiento por anclaje en CSS. Al final sabrá qué partes de una biblioteca de herramientas de ayuda puede eliminar y cuáles aún son necesarias.
Tres problemas ocultos dentro de una herramienta de ayuda
Cada uno de ellos representa una limitación antigua y bien conocida en el comportamiento del navegador antes de la llegada de las nuevas especificaciones:
- Apilamiento. ¿La herramienta de ayuda se mostrará por encima de todo lo demás, o el atributo
overflow: hiddende algún elemento padre la recortará? - Posicionamiento. ¿Sabe la herramienta de ayuda dónde se encuentra su elemento desencadenante en la pantalla, y sigue esa posición al desplazarse o cambiar el tamaño?
- Cierre. ¿Se cierra automáticamente cuando el usuario hace clic en otro lugar o presiona Escape?
Durante casi toda la historia de la web, cada proyecto que necesitaba una herramienta de ayuda, un menú desplegable o una lista de autocompletado resolvía los tres problemas con JavaScript. Las soluciones varían para cada caso, y tratarlos como un único problema es precisamente cómo un pequeño detalle de la interfaz se convirtió en una dependencia. Por eso vale la pena analizarlos uno por uno.
Apilamiento: por qué z-index no puede escapar de su contexto
Cada elemento pertenece a un contexto de apilamiento, el cual determina qué se dibuja sobre qué. z-index ordena los elementos dentro de un mismo contexto de apilamiento; no puede sacar a un elemento del contexto al que pertenece.
Ponga la herramienta de ayuda dentro de un contenedor con overflow: hidden, o dentro de un modal que cree su propio contexto de apilamiento, y ningún valor, ni siquiera 999999, hará que aparezca por encima de ese límite. La herramienta de ayuda está sujeta a las reglas de renderizado de sus ancestros, y z-index no tiene alcance más allá de ellas.
La solución tradicional era un portal: se colocaba el marcado de la herramienta de ayuda en otro punto del documento, generalmente al final de <body>, para que ya no heredara el recorte y el apilamiento del elemento padre. React incluye createPortal principalmente por esta razón. Se trata más bien de una solución temporal para algo que CSS no podía resolver que de una característica propia de React.
Posicionamiento: el posicionamiento absoluto solo entiende a los ancestros
position: absolute coloca un elemento en relación con su ancestro posicionado más cercano, es decir, el elemento más próximo en la estructura cuya position sea relative, absolute, fixed o sticky. La palabra clave es ancestro: la referencia debe encontrarse en la misma rama del DOM, por encima de la herramienta de ayuda.
En cuanto la herramienta de ayuda y su desencadenante se convierten en elementos hermanos, o la herramienta de ayuda es trasladada a <body> para resolver el problema de superposición, el desencadenante deja de ser un ancestro. CSS no tenía forma de indicar “posicionar esto en relación con ese elemento no relacionado allí”. La debilidad nunca estuvo en el posicionamiento de CSS en general, sino en la ausencia de cualquier relación de posicionamiento que ignorara la estructura del documento.
Las bibliotecas llenaron ese vacío mediante mediciones. Ellas llaman a getBoundingClientRect() para obtener el rectángulo relativo al área de visualización del desencadenante, derivan coordenadas para la herramienta de ayuda y vuelven a realizar ese cálculo en cada desplazamiento o cambio de tamaño, ya que las cifras siguen cambiando. Ese bucle continuo de medición y colocación es prácticamente todo lo que hace una biblioteca de posicionamiento en tiempo de ejecución.
Cierre: comportamiento que el marcado no podía describir
Antes de la API Popover, HTML y CSS no tenían concepto alguno de “cerrarse al hacer clic fuera o presionar Escape”. Todo se gestionaba mediante scripts: un listener de clic en document que verificaba si el destino del evento estaba fuera de la herramienta de ayuda, un listener de keydown que esperaba la tecla Escape, y código de limpieza para ambos cuando el componente se desmontaba a fin de evitar fugas de recursos. A diferencia de los dos primeros problemas, este no involucra aspectos geométricos; se trata puramente de comportamiento, pero sigue siendo un tercer bloque de código en tiempo de ejecución que el navegador no proporcionaba.
Popovers versus modales
Es útil definir la terminología antes de analizar la sintaxis. Un popover es cualquier elemento que se muestra por encima del resto de la página, está posicionado en relación con un desencadenante y se cierra al hacer clic fuera de él o presionar Escape. Las sugerencias, los menús desplegables, las listas de autocompletado y los menús contextuales son todos popovers con estilos diferentes pero con los mismos tres problemas técnicos.
Un modal comparte el problema de superposición, pero no es un popover, ya que bloquea el contenido. Mientras está abierto, el contenido detrás de él queda inactivo: los usuarios no pueden dirigir la atención hacia él, hacer clic en él ni desplazarse por él, y generalmente hay un fondo que mantiene el foco dentro del modal hasta que este se cierra. Piense en un mensaje de “confirmar eliminación”: nada más en la página es utilizable hasta que se responde. Un popover no bloquea nada; la página sigue siendo completamente interactiva, y el popover simplemente se cierra cuando el usuario pasa a otra cosa.
Las especificaciones reflejan directamente esta distinción:
popover="auto"permite cerrar el elemento de forma sencilla sin bloquear nada: se cierra al hacer clic fuera de él o presionar Escape, dejando el foco libre. Las herramientas de ayuda y los menús desplegables pertenecen a esta categoría.popover="manual"permanece abierto hasta que tu script lo cierre, sin posibilidad de cerrarlo de forma sencilla, lo cual es adecuado para notificaciones persistentes.- Un
<dialog>abierto con.showModal()es la variante que bloquea: se coloca en la capa superior, cuenta con un fondo de pantalla, retiene el foco y hace que todo lo que está detrás quede inactivo. - Al abrir ese
<dialog>mediante.show(), en cambio, se obtiene un elemento que no bloquea y se comporta de manera muy similar a un popover.
Costos del enfoque basado en JavaScript
Los tamaños comprimidos que se muestran a continuación provienen de npm y solo incluyen las bibliotecas en sí:
popper.js (v1, now deprecated) 7.1 KB
Tippy.js (bundles @popperjs/core) 14.1 KB
Floating UI, vanilla (@floating-ui/dom) 8.1 KB
Floating UI, React bindings 30.1 KB
react-tooltip (@floating-ui/dom + clsx) 14.1 KB
Estos números excluyen todo lo que se añade encima: la configuración, el componente contenedor, los estilos CSS para flechas y temas. Ese es el costo base antes de aplicar cualquier lógica propia, y un proyecto que combine, por ejemplo, un paquete de tooltips con un paquete separado de menús desplegables debe pagarlo dos veces.
Nada de esto refleja una mala ingeniería. La interfaz flotante en particular está diseñada cuidadosamente; su función principal es gestionar correctamente el despliegue en caso de sobrecarga en todos los navegadores, a pesar de sus peculiaridades. El costo surgió por la necesidad de resolver tres problemas no relacionados simultáneamente en JavaScript, ya que la plataforma no ofrecía otra alternativa.
La API Popover se encarga del apilamiento y cierre
Dos especificaciones separadas reemplazaron a la biblioteca, y no dividen el trabajo de la manera que uno podría esperar. El atributo popover se encarga del apilamiento y cierre al mismo tiempo, con casi ningún script adicional.
<button popovertarget="my-tooltip">Hover me</button>
<div id="my-tooltip" popover="auto">
This is the tooltip content.
</div>
El atributo popovertarget conecta el botón con el elemento que tiene el id correspondiente. Con popover="auto", el elemento se coloca en la capa superior del navegador al abrirse, lo cual representa exactamente la forma de sortear el efecto de overflow: hidden y los contextos de apilamiento mencionados anteriormente; además, se puede cerrar fácilmente con un clic fuera del elemento o presionando la tecla Escape, sin necesidad de código adicional.
Una corrección en la redacción de la documentación: popovertarget se activa al hacer clic, tocar o presionar teclas, no al pasar el cursor por encima. Una verdadera herramienta de ayuda que aparece al pasar el cursor sigue necesitando un pequeño fragmento de código para llamar a showPopover() y hidePopover() en los eventos de puntero y foco, o a un mecanismo declarativo más reciente una vez que los navegadores objetivo lo soporten. Aun así, el apilamiento y la eliminación ya no requieren una biblioteca. La posición es el único aspecto pendiente, y corresponde a otra especificación.
Posicionamiento de anclajes: vincula elementos por nombre
El Posicionamiento de Anclajes en CSS aborda el único problema que la API Popover deja sin resolver. Permite que cualquier par de elementos en el documento se refieran mutuamente por nombre, en lugar de a través de una estructura de padre e hijo.
.trigger {
anchor-name: --my-anchor;
}
.tooltip {
position: absolute;
position-anchor: --my-anchor;
top: anchor(--my-anchor bottom);
left: anchor(--my-anchor left);
}
anchor-name registra el desencadenante bajo un identificador con guiones, la misma sintaxis que se utiliza para las propiedades personalizadas. position-anchor en la herramienta de información apunta a ese nombre, y la función anchor() lee un borde específico del anclaje (top, right, bottom, left o center) para que la herramienta de información se alinee con él.
Lo importante es que ninguno de los elementos necesita contener al otro. El navegador ahora realiza de forma nativa lo que antes getBoundingClientRect() tenía que calcular manualmente en cada frame de desplazamiento. Al combinar esto con un popover, recuerde que la hoja de estilo del agente del usuario aplica a los elementos [popover] las propiedades inset: 0 y margin: auto para centrarlos; si la herramienta de información ignora los desplazamientos del anclaje, restablecer esas propiedades suele ser la solución.
Activar el rebote sin un listener de desplazamiento
La parte de una biblioteca de posicionamiento que contiene la mayor parte de su lógica es el manejo del desbordamiento: detectar que la herramienta de ayuda está a punto de salirse del área visible y elegir primero otro lugar para colocarla. La funcionalidad de anclaje de posición aborda esto mediante position-try-fallbacks.
.tooltip {
position: absolute;
position-anchor: --my-anchor;
position-area: top center;
position-try-fallbacks: flip-block, flip-inline;
}
Aquí position-area: top center establece la ubicación predeterminada, y position-try-fallbacks enumera las alternativas que el navegador prueba en orden cuando dicha ubicación provocaría desbordamiento dentro del bloque contenedor o de la área visible. flip-block refleja la herramienta de ayuda a lo largo del eje del bloque, de modo que la parte superior se convierte en inferior, y flip-inline la refleja a lo largo del eje en línea, de modo que la parte izquierda se convierte en derecha. El navegador vuelve a evaluar esto durante el proceso de maquetación, sin un gestor de desplazamiento ni un script en el hilo principal que detecte el desbordamiento.
Cuando un espejo simple no es suficiente, la regla @position-try permite definir ubicaciones de respaldo con nombre, cada una siendo un pequeño bloque de declaraciones de posicionamiento que el navegador puede recorrer sucesivamente.
@position-try --below {
position-area: bottom center;
margin-top: 8px;
}
@position-try --above {
position-area: top center;
margin-bottom: 8px;
}
.tooltip {
position-anchor: --my-anchor;
position-try-fallbacks: --above, --below;
}
Esta es la misma decisión que toma Floating UI en JavaScript con cada evento de desplazamiento, declarada de antemano como datos que el motor de diseño evalúa. Tenga en cuenta que la regla .tooltip en este fragmento asume que el elemento ya está posicionado de forma absoluta o fija, como en los ejemplos anteriores; la posición anclada no tiene efecto en elementos con posicionamiento estático.
Cuándo sigue siendo justificado usar una biblioteca de posicionamiento
Para una herramienta de ayuda, un menú desplegable simple o una lista de autocompletado, la combinación nativa es ahora un valor por defecto razonable. El papel de la biblioteca se ha reducido, aunque no ha desaparecido.
El soporte de los navegadores es la primera limitación. Los navegadores Chromium han admitido el posicionamiento de anclajes desde la versión 125, y @position-try alcanzó el nivel básico más tarde que anchor() en sí. El soporte en Safari y Firefox llegó posteriormente, y las cifras citadas en Internet difieren, por lo que es mejor consultar una tabla de compatibilidad actual en lugar de confiar en alguna lista específica de versiones. Cuando falta soporte, no existe una solución gradual basada únicamente en CSS: un navegador que no entiende anchor() simplemente no logra posicionar el elemento. Si es necesario soportar versiones antiguas de Safari o navegadores móviles con motores obsoletos, mantenga una solución alternativa; nuestra guía para implementar CSS moderno de forma segura aborda la detección de funcionalidades y el mejoramiento progresivo para los anclajes.
El segundo caso son las reglas de colocación que van más allá del reflejo en casos de desbordamiento: un panel flotante que contiene una lista virtualizada, donde se verifican colisiones contra múltiples límites simultáneamente, o una colocación determinada por los datos de la aplicación en lugar del diseño. El script puede reaccionar ante cualquier estado que mantenga la aplicación, mientras que una lista de respaldo fija en CSS solo tiene en cuenta el diseño.
En el caso habitual, que abarca la mayoría de los proyectos, resulta difícil justificar el uso de 8 a 30 KB de JavaScript para tareas que ahora el navegador realiza por sí mismo.
Puntos clave
- Una herramienta de ayuda presenta tres problemas: apilamiento, posicionamiento y cierre. Existían bibliotecas porque todos estos tres aspectos tenían que resolverse mediante script.
popover="manual" y con .showModal() abordan los casos de permanencia y bloqueo.position-try-fallbacks junto con @position-try reemplazan la lógica de cambio al sobrescribirse que dominaba los tiempos de ejecución de las bibliotecas.Lecturas relacionadas
- APIs nativas del navegador que sustituirán a paquetes populares de npm en 2026 — Explica cómo las características nativas de JavaScript y CSS como Signals, el operador pipeline, Temporal y el posicionamiento por anclaje están reemplazando a paquetes comunes de npm.
- Distribuyendo CSS moderno de forma segura: anclajes, lanes de rejilla, alcance y light-dark() — Aprenda cómo el posicionamiento por anclaje, los elementos emergentes, la disposición en lanes de rejilla, las transiciones de vista, light-dark() y @scope sustituyen a las bibliotecas de JavaScript, y cómo adoptar cada uno de ellos con soluciones alternativas.