CSS Anchor Positioning y Popover API: Adiós al JS para Popups en 2026

前端工程

CSS Anchor Positioning: La "Proclamación de Emancipación" para Desarrolladores Frontend

Todo desarrollador frontend ha experimentado este dolor: usar JS para calcular posiciones de Tooltip, manejar desbordamiento de límites, escuchar eventos de scroll y resize... Ahora, CSS puede manejar todo esto de forma nativa.

Revisión histórica: En 2016 usábamos Popper.js, en 2020 Floating UI, y en 2026 finalmente tenemos posicionamiento de ancla CSS nativo. Después de 10 años, el posicionamiento de popups ya no es trabajo de JS.

Evolución del Posicionamiento con JS al Posicionamiento con CSS

2016    jQuery offset() + cálculo manual
        Recalcular en cada scroll/resize

2020    Floating UI / Popper.js
        Auto-flip, offset, detección de límites
        Pero aún requiere init y destroy con JS

2024    CSS Anchor Positioning (Chrome 125+)
        Posicionamiento declarativo CSS puro
        El navegador maneja límites y scroll nativamente

2026    Anchor Positioning + Popover API
        Popups CSS puro: toggle sin JS, posicionamiento sin JS
        Solución declarativa completa de popups

anchor-name y position-anchor

Uso Básico

<div class="card">
  <button class="trigger">Más Acciones</button>
  <div class="dropdown">Contenido desplegable</div>
</div>
.trigger {
  anchor-name: --my-anchor;
}

.dropdown {
  position: fixed;
  position-anchor: --my-anchor;
  top: anchor(bottom);
  left: anchor(left);
}

Referencia de la Función anchor()

.tooltip {
  position: fixed;
  position-anchor: --trigger-anchor;

  /* Relativo a los bordes del ancla */
  top: anchor(bottom);
  bottom: anchor(top);
  left: anchor(left);
  right: anchor(right);

  /* Puntos centrales del ancla */
  left: anchor(50%);
  top: anchor(50%);

  /* Con offset */
  top: calc(anchor(bottom) + 8px);
  left: calc(anchor(left) - 4px);
}

position-area: Posicionamiento en Cuadrícula de Nueve Celdas

Sintaxis de Nueve Celdas

┌─────────────┬─────────────┬─────────────┐
│  top left   │   top center│  top right  │
│             │             │             │
├─────────────┼─────────────┼─────────────┤
│ center left │   center    │ center right│
│             │             │             │
├─────────────┼─────────────┼─────────────┤
│ bottom left │bottom center│ bottom right│
│             │             │             │
└─────────────┴─────────────┴─────────────┘
.tooltip-below {
  position-area: bottom center;
}

.tooltip-right {
  position-area: center right;
}

.dropdown-below-left {
  position-area: bottom left;
}

position-fallback y Estrategia de Fallback Multinivel

Auto-Flip por Desbordamiento de Límites

.dropdown {
  position: fixed;
  position-anchor: --trigger;

  /* Preferido: debajo del ancla */
  top: anchor(bottom);
  left: anchor(left);

  position-fallback: --dropdown-fallback;
}

@position-fallback --dropdown-fallback {
  /* Fallback 1: encima del ancla */
  @try {
    bottom: anchor(top);
    left: anchor(left);
  }

  /* Fallback 2: debajo del ancla, alineado a la derecha */
  @try {
    top: anchor(bottom);
    right: anchor(right);
  }

  /* Fallback 3: encima del ancla, alineado a la derecha */
  @try {
    bottom: anchor(top);
    right: anchor(right);
  }
}

Popover API: Capa de Popover Nativa

Uso Básico

<button popovertarget="my-popover">Abrir Popover</button>

<div id="my-popover" popover>
  <p>Este es contenido de Popover nativo</p>
  <button popovertarget="my-popover" popovertargetaction="hide">Cerrar</button>
</div>
[popover] {
  position: fixed;
  inset: unset;
  margin: 0;
  padding: 16px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  background: white;
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}

Combinación Popover + Anchor

<button class="menu-trigger" popovertarget="menu-popover">Menú</button>

<div id="menu-popover" popover class="menu-dropdown">
  <a href="/profile">Perfil</a>
  <a href="/settings">Configuración</a>
  <a href="/logout">Cerrar Sesión</a>
</div>
.menu-trigger {
  anchor-name: --menu-anchor;
}

.menu-dropdown {
  position-anchor: --menu-anchor;
  position-area: bottom left;
  margin: 4px;
}

anchor-size(): Auto-Dimensionamiento Basado en el Elemento Ancla

.trigger {
  anchor-name: --select-anchor;
}

.select-dropdown {
  position: fixed;
  position-anchor: --select-anchor;
  top: anchor(bottom);
  left: anchor(left);

  /* El ancho del desplegable sigue al botón trigger */
  width: anchor-size(width);

  /* O establecer ancho mínimo */
  min-width: anchor-size(width);
}

Práctica: Componentes UI Comunes en CSS Puro

Tooltip

<span class="tooltip-trigger" tabindex="0">
  Ayuda
  <span class="tooltip-content" role="tooltip">Este es un tooltip de ayuda</span>
</span>
.tooltip-trigger {
  anchor-name: --tt-anchor;
  position: relative;
  cursor: help;
  text-decoration: underline dotted;
}

.tooltip-content {
  position: fixed;
  position-anchor: --tt-anchor;
  position-area: top center;
  margin: 8px;
  padding: 6px 10px;
  background: #1f2937;
  color: white;
  border-radius: 4px;
  font-size: 13px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s;

  position-fallback: --tt-fallback;
}

.tooltip-trigger:hover .tooltip-content,
.tooltip-trigger:focus .tooltip-content {
  opacity: 1;
}

@position-fallback --tt-fallback {
  @try { position-area: bottom center; }
  @try { position-area: center right; }
  @try { position-area: center left; }
}

Menú Desplegable

<button class="dropdown-trigger" popovertarget="dd-menu">
  Opciones ▾
</button>
<div id="dd-menu" popover class="dropdown-menu">
  <button class="dropdown-item">Editar</button>
  <button class="dropdown-item">Copiar</button>
  <button class="dropdown-item">Eliminar</button>
</div>
.dropdown-trigger {
  anchor-name: --dd-anchor;
}

.dropdown-menu {
  position-anchor: --dd-anchor;
  position-area: bottom left;
  margin: 4px;
  width: anchor-size(width);
  min-width: 160px;
  padding: 4px;
  border-radius: 8px;
  background: white;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);

  position-fallback: --dd-fallback;
}

.dropdown-item {
  display: block;
  width: 100%;
  padding: 8px 12px;
  border: none;
  background: none;
  text-align: left;
  border-radius: 4px;
  cursor: pointer;
}

.dropdown-item:hover {
  background: #f3f4f6;
}

@position-fallback --dd-fallback {
  @try { position-area: top left; }
}

Comparación con Floating UI/Popper.js

Dimensión CSS Anchor + Popover Floating UI
Dependencia JS Cero JS 12KB gzip
Rendimiento Nativo del navegador Cálculo JS
Declarativo Declarativo CSS Imperativo JS
Flip de límites position-fallback middleware flip()
Offset margin/anchor() middleware offset()
Flecha Pseudo-elemento CSS Elemento SVG/HTML
Elementos virtuales No soportado Soportado
Contenido dinámico Auto-seguimiento Necesita update()
Soporte de navegadores Chrome 125+ Todos los navegadores
Compatible con SSR Totalmente compatible Necesita JS del cliente

Cuándo Aún Necesitas Librerías JS

Escenarios que aún requieren Floating UI:
1. Necesitas soportar navegadores antiguos (Safari < 17.5, Firefox < 131)
2. Necesitas posicionamiento de elementos virtuales (seguimiento del cursor)
3. Necesitas cadenas complejas de middleware (autoUpdate, size, hide)
4. Requisitos de wrapper declarativo para React/Vue

Escenarios donde CSS Anchor funciona:
1. Tooltip, Dropdown, Popover y otros popups estándar
2. Solo necesitas soportar navegadores modernos
3. Persiguiendo cero dependencia JS
4. Escenarios sensibles al rendimiento

Compatibilidad de Navegadores y Polyfill

Estado de Soporte

Navegador Anchor Positioning Popover API
Chrome 125+
Edge 125+
Safari 17.5+
Firefox 131+
iOS Safari 17.5+

Soluciones de Polyfill

<script src="https://cdn.jsdelivr.net/npm/@oddbird/css-anchor-positioning@0.3.0/dist/css-anchor-positioning.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@oddbird/popover-polyfill@0.4.0/dist/popover.min.js"></script>

Combinación con @starting-style para Animaciones de Popup

[popover] {
  opacity: 0;
  transform: translateY(-8px) scale(0.95);
  transition: opacity 0.2s, transform 0.2s, display 0.2s allow-discrete;
  overlay: none;
}

[popover]:popover-open {
  opacity: 1;
  transform: translateY(0) scale(1);
  overlay: auto;
}

@starting-style {
  [popover]:popover-open {
    opacity: 0;
    transform: translateY(-8px) scale(0.95);
  }
}

Resumen

  1. CSS Anchor Positioning es la solución definitiva para el posicionamiento de popups — Cero JS, declarativo, nativo del navegador
  2. La Popover API hace que el toggle de popups también sea cero JS — el atributo popovertarget lo maneja en una línea
  3. position-fallback resuelve el desbordamiento de límites — No más cálculos JS para el flip
  4. Todos los navegadores principales lo soportan en 2026 — Seguro para usar en producción

Después de 10 años, el posicionamiento de popups ha vuelto finalmente a donde pertenece: CSS. Esto no es una mejora incremental — es un cambio de paradigma, tal como Flexbox reemplazó los layouts con float.

Prueba estas herramientas que se ejecutan en tu navegador — no requieren registro →

#CSS锚点定位#Anchor Positioning#Popover API#弹出层#CSS