Cuadros de información con reconocimiento de posición

Cuando se crean cuadros de información o ventanas emergentes con el posicionamiento de anclas de CSS, el navegador puede "voltear" automáticamente el elemento a una posición de resguardo si, de lo contrario, se desbordaría el viewport. Cuando esto sucede, es posible que quieras ajustar el estilo del contenido posicionado, por ejemplo, para cambiar la posición de una flecha que apunta desde el contenido posicionado hasta el ancla.

Las consultas de contenedores fijos resuelven este problema, ya que te permiten consultar el estado de posicionamiento activo de un elemento y aplicar los estilos correspondientes. Para obtener información sobre el posicionamiento general de los menús desplegables y flotantes fijos con inversión de colisión de viewport (sin cambio de estilo de la flecha direccional), consulta Menús desplegables adaptados al viewport.

El problema

Imagina una información sobre la herramienta que aparece sobre su ancla de forma predeterminada. Tiene una flecha hacia abajo en la parte inferior. Si el usuario se desplaza y la información sobre la herramienta se invierte para aparecer debajo del ancla, la flecha ahora apunta en la dirección incorrecta y está en el lado equivocado de la información sobre la herramienta.

La solución: Consultas de contenedores fijadas

Si configuras container-type: anchored en tu elemento posicionado, lo conviertes en un contenedor de consultas que conoce su propio estado de posición de ancla. Luego, puedes usar la consulta @container anchored() para actualizar sus descendientes o seudoelementos.

1. Crea la información sobre la herramienta y el activador

Usa la API de Popover para crear un cuadro de información. Invocar el cuadro emergente a través de popovertarget establece automáticamente una conexión de anclaje implícita con el botón activador, por lo que no se requiere un anchor-name o position-anchor explícito.

<button popovertarget="tooltip" id="anchor" aria-describedby="tooltip">anchor</button>
<div id="tooltip" popover role="tooltip"></div>

Restablece los estilos de margen y de inserción del cuadro emergente para usarlos con el posicionamiento de anclaje, pero solo si se admite el posicionamiento de anclaje.

@supports (anchor-name: --my-anchor) {
  [popover] {
    inset: auto;
    margin: unset;
  }
}

2. Configura el contenedor

Aplica container-type: anchored al elemento que se está posicionando. Este elemento también debe tener definido position-try-fallbacks para habilitar el comportamiento de volteo.

#tooltip {
  position: fixed;
  position-area: block-start;
  position-try-fallbacks: flip-block;

  /* Enable anchored container queries */
  container-type: anchored;
}

3. Aplica un diseño basado en el resguardo

Usa @container anchored(fallback: <value>) para aplicar estilos cuando un resguardo específico esté activo.

Al igual que todas las consultas de contenedores, @container solo puede aplicar diseño a los elementos subordinados del contenedor. Una estrategia común para crear las flechas es con los seudoelementos ::before y ::after, que se tratan como descendientes y se les puede aplicar diseño directamente. Sin embargo, para aplicar diseño a la información sobre la herramienta en sí (como se ve en el paso 4), agregaremos un elemento secundario a la información sobre la herramienta y crearemos la flecha en su pseudoelemento ::before.

<div id="tooltip" popover role="tooltip">
  <div class="tooltip-content">Tooltip</div>
</div>
.tooltip-content::before {
  /* Default "down" arrow for the 'top' position */
  content: "▼";
  position: absolute;
  inset-block-end: 0;
  inset-inline-start: 1rem;
}

/* Update to an "up" arrow when the 'flip-block' fallback (bottom) is active */
@container anchored(fallback: flip-block) {
  .tooltip-content::before {
    content: "▲";
    inset-block-start: 0;
    inset-block-end: auto;
  }
}

4. Cómo aplicar diseño al contenedor

Si necesitas cambiar las propiedades del contenedor (como margin o background-color) cuando se gira, debes usar un elemento de wrapper interno.

  1. Aplica container-type: anchored al elemento posicionado exterior.
  2. Orienta el elemento interno dentro del bloque @container.
@container anchored(fallback: flip-block) {
  .tooltip-content {
    border-radius: 0 0 .5rem .5rem;
    margin-block-start: 0.25rem;
  }
}

Prácticas recomendadas

  • Prefiere las alternativas lógicas: Usa palabras clave como flip-block y flip-inline en position-try-fallbacks para realizar consultas más sencillas que controlen automáticamente el texto de derecha a izquierda y los diferentes modos de escritura.
  • Usa pseudoelementos para las flechas: Las flechas de los cuadros de información son puramente decorativas y son candidatas perfectas para ::before o ::after, a las que se les puede aplicar diseño a través de consultas de contenedores anclados sin DOM adicional.

Estrategias de resguardo

Posicionar la flecha según el resguardo aplicado es una mejora progresiva, y no hay otra forma de reaccionar a la posición de resguardo. Para ocultar la flecha en los navegadores que no admiten consultas de contenedores de posición de anclaje, prueba la compatibilidad con CSS con @supports (container-type: anchored).

@supports (container-type: anchored) {
  .tooltip-content::before {
    content: "▼";
  }
}

Resguardos y compatibilidad de navegadores para Popover

La API de Popover es, en su mayor parte, una mejora progresiva, pero sus comportamientos definitorios (promoción de la capa superior, cierre rápido y la invocación de popovertarget) no tienen un equivalente solo de CSS. Los navegadores más antiguos necesitan un polyfill o un resguardo manual si prefieres no enviar uno.

Polyfill: Para admitir el atributo popover en navegadores más antiguos, carga @oddbird/popover-polyfill de forma condicional. OBLIGATORIO: Detecta la función verificando la propiedad popover en HTMLElement.prototype y carga el polyfill solo cuando no haya compatibilidad nativa. NO lo cargues de forma incondicional.

Con un empaquetador o un mapa de importación:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

Sin un empaquetador, importa desde una CDN dentro de un <script type="module">:

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

Advertencia de diseño: El polyfill no puede definir la pseudoclase :popover-open real, por lo que aplica una clase .\:popover-open en su lugar. OBLIGATORIO: Combina las dos con :is() o :where(); de lo contrario, los navegadores que no tengan :popover-open descartarán toda la regla:

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

Como alternativa, para un resguardo heredado sin un polyfill, usa position: fixed y calcula manualmente las coordenadas a través de getBoundingClientRect() o confía en el posicionamiento predeterminado con inset: auto si eso es aceptable para el caso de uso.

Los navegadores que no admiten la API de Popover tampoco admiten el posicionamiento de anclaje, por lo que la información sobre la herramienta aparecerá en el centro de la pantalla.