wandres.dev
ANCHOR POSITIONING · Anclar sin JavaScript

La función anchor() y sus lados

Cómo se lee la geometría del ancla desde una propiedad de desplazamiento, qué significa cada lado, el porcentaje, el valor de reserva y anchor-center.

⏱ 18 min

anchor() es la pieza que convierte una relación entre dos elementos en una posición concreta. Devuelve una longitud: la coordenada de un lado del ancla, expresada en el sistema del bloque contenedor del elemento anclado. Se escribe dentro de las propiedades de desplazamiento y de margen, y su gramática es más pequeña de lo que parece: un lado, opcionalmente un nombre de ancla, y opcionalmente un valor de reserva.

🎯 Al terminar esta lección sabrás
  • Escribir anchor() con sus tres formas de argumento.
  • Traducir un lado del ancla a la propiedad de desplazamiento correcta.
  • Usar porcentajes y calc() dentro de anchor().
  • Centrar un elemento sobre su ancla con anchor-center.

Qué devuelve y dónde se escribe

.tooltip {
  position: absolute;
  position-anchor: --boton;
  inset-block-end: anchor(top);       /* mi borde inferior en el borde superior del ancla */
  inset-inline-start: anchor(left);   /* mi borde izquierdo en el izquierdo del ancla */
}

La sintaxis completa es anchor(<nombre>? <lado>, <reserva>?). Si omites el nombre, se usa el ancla por defecto declarada en position-anchor. Si añades un valor de reserva tras la coma, se usa cuando la función no puede resolverse: no hay ancla válida, o el lado pedido no aplica.

Los lados aceptados son físicos —top, right, bottom, left—, lógicos —start, end, self-start, self-end—, el center, y un porcentaje.

El punto que hay que interiorizar: anchor() devuelve una posición absoluta en el eje, no un desplazamiento relativo. Y las propiedades de desplazamiento miden desde su propio borde del bloque contenedor. Por eso inset-block-end: anchor(top) significa “coloca mi borde inferior donde está el borde superior del ancla”: la propiedad habla de mi borde inferior, la función da la coordenada de su borde superior.

De ahí sale la regla mnemotécnica que evita la mitad de los errores: para pegar el tooltip encima del ancla, usas la propiedad opuesta al lado que pides.

dónde quieres el anclado propiedad lado
encima inset-block-end top
debajo inset-block-start bottom
a la izquierda inset-inline-end left
a la derecha inset-inline-start right

Y cuando quieres alinear en el otro eje en lugar de apilar, la propiedad y el lado coinciden:

/* debajo del ancla, alineado por la izquierda */
.menu {
  inset-block-start: anchor(bottom);
  inset-inline-start: anchor(left);
}

/* debajo del ancla, alineado por la derecha */
.menu {
  inset-block-start: anchor(bottom);
  inset-inline-end: anchor(right);
}

Porcentajes, calc y varias anclas

El lado puede ser un porcentaje, que interpola entre el borde inicial y el final del ancla en ese eje. anchor(50%) es el centro y equivale a anchor(center).

.punta { inset-inline-start: anchor(25%); }

anchor() se puede meter dentro de calc(), lo que da el hueco entre ancla y anclado sin recurrir a márgenes:

.tooltip { inset-block-end: calc(anchor(top) + .5rem); }

Aunque para el hueco, la forma más legible es un margen normal, que el motor ya tiene en cuenta:

.tooltip { inset-block-end: anchor(top); margin-block-end: .5rem; }

Y un mismo elemento puede leer de anclas distintas en cada propiedad, sin más que nombrarlas. Es la forma de hacer que un elemento se estire entre dos referencias:

.regla {
  position: absolute;
  inset-inline-start: anchor(--inicio right);
  inset-inline-end:   anchor(--fin left);
  inset-block-start:  anchor(--inicio top);
}

Ese patrón —una barra que abarca desde el borde derecho de un elemento hasta el borde izquierdo de otro— era prácticamente imposible sin medir en JavaScript, y aquí son tres declaraciones.

anchor-center: el centrado que faltaba

Alinear el centro del anclado con el centro del ancla es el caso más común y el que peor se expresaba con desplazamientos, porque exige conocer el tamaño del anclado. La especificación añadió un valor de alineación específico: anchor-center, aceptado por justify-self, align-self, justify-items, align-items y place-self.

.tooltip {
  position: absolute;
  position-anchor: --boton;
  inset-block-end: anchor(top);
  justify-self: anchor-center;      /* centrado horizontal sobre el ancla */
  margin-block-end: .5rem;
}

Sin anchor-center, ese centrado exigía inset-inline-start: anchor(center) más un translate: -50%, con la consecuencia de que translate quedaba ocupado y el elemento pasaba a crear contexto de apilamiento y bloque contenedor. Con la palabra clave, nada de eso ocurre.

Ojo con el eje: justify-self alinea en el eje en línea y align-self en el de bloque. Para un tooltip que va encima y centrado horizontalmente, el que necesitas es justify-self.

flowchart TB
A[Donde quieres el anclado] --> B{Apilado en un eje o alineado}
B -->|Apilado encima o debajo| C[Propiedad opuesta al lado pedido]
C --> D[inset-block-end anchor top para ponerlo encima]
B -->|Alineado por un borde| E[Propiedad y lado coinciden]
E --> F[inset-inline-start anchor left para alinear por la izquierda]
B -->|Centrado sobre el ancla| G[justify-self o align-self con anchor-center]
G --> H[Sin translate y sin contexto de apilamiento]
style A fill:#89b4fa,color:#11111b
style D fill:#a6e3a1,color:#11111b
style F fill:#a6e3a1,color:#11111b
style H fill:#a6e3a1,color:#11111b

El valor de reserva

El segundo argumento se usa cuando anchor() no puede resolverse. Los dos casos son que no exista ninguna ancla con ese nombre, o que la referencia sea inválida por las reglas de árbol.

.panel {
  position: fixed;
  inset-block-start: anchor(--barra bottom, 4rem);
}

Si --barra existe, el panel se coloca justo debajo; si no, cae a cuatro remes del borde. Es la forma limpia de escribir un componente que funciona tanto anclado como suelto.

Sin valor de reserva, una función anchor() que no se resuelve hace que la declaración entera sea inválida en tiempo de computación, y la propiedad toma su valor heredado o inicial. Eso significa auto en un desplazamiento, es decir, la posición estática: el elemento se queda donde estaba en el flujo. No desaparece ni salta a la esquina, lo cual es un fallo bastante benigno, pero rara vez es lo que quieres.

anchor() no es una consulta al DOM, es un valor de layout, y la diferencia se nota en tres sitios

La tentación es leer anchor(top) como “dame el getBoundingClientRect().top del ancla”. Se parece, pero no es lo mismo, y las tres diferencias importan. Primera: el sistema de coordenadas. anchor() devuelve la coordenada expresada en el bloque contenedor del anclado, no en el viewport. Si el anclado vive dentro de un ancestro transformado, el valor ya viene traducido a ese sistema, y todo encaja sin que tú hagas ninguna corrección. Con getBoundingClientRect() tendrías que restar a mano el rectángulo del contenedor y compensar la transformación. Segunda: el momento. El valor no es una foto tomada tras el layout, es una entrada del propio layout; el motor lo resuelve en la misma pasada, lo que elimina el fotograma de retraso y hace que el anclado se mueva pegado al ancla incluso durante un scroll acelerado por composición. Tercera: la validez. La especificación define con precisión cuándo una referencia es utilizable, y cuando no lo es, la función se resuelve al valor de reserva o la declaración se invalida; nunca produce una posición incorrecta silenciosa. Compáralo con el caso equivalente en JavaScript, donde un ancla que todavía no está en el DOM, o que está oculta con display: none, devuelve un rectángulo de ceros y coloca tu tooltip en la esquina superior izquierda sin decir nada. Esa diferencia entre fallar de forma definida y devolver ceros es, en la práctica, la mitad del valor de la característica.

⚔️ Coloca con precisión
  1. Coloca un tooltip encima de un botón usando inset-block-end: anchor(top) y comprueba la regla de la propiedad opuesta.
  2. Cámbialo a inset-block-start: anchor(top) y explica el resultado observado.
  3. Céntralo con justify-self: anchor-center y verifica que translate sigue libre.
  4. Estira una barra entre dos anclas distintas con anchor(--a right) y anchor(--b left).
  5. Escribe un anchor() con un nombre que no existe, con y sin valor de reserva, y compara los dos comportamientos.