When you're placing a tooltip or a drop-down menu, you often want to position it relative to another element on the page. While there have been ways using absolute positioning to achieve this effect, more complex requirements have historically resorted to positioning items using JavaScript.
CSS anchor positioning provides a way to declaratively position an element relative to another element.
Tethering elements
To make an element an anchor, you give it an anchor-name
value of any string that starts with two dashes. This is the identifier that the positioned element will use to find its anchor, and it's helpful to give it a descriptive name. You can even give an element multiple anchor names, if it will be used as an anchor in different ways.
You will need to set a few properties on the positioned element so it can be tethered. First, you need to pull the element out of the document's flow, so that it floats, by setting position: absolute
or position: fixed
.
Next, you will need to set which anchor you want to tether to, by setting position-anchor
to the anchor name you set on the anchor.
Finally, you'll need to set how to position the anchor. You'll learn more about position-area
later in this module.
#anchor {
anchor-name: --my-anchor;
}
#positionedElement {
position: absolute;
position-anchor: --my-anchor;
position-area: end;
}
Implicit tethers
Popovers are even simpler to tether. When you open a popover using a button with a popovertarget
or by setting a source
with showPopover({source})
, the popover has an "implicit anchor" already set. Because a popover is already floating with position: fixed
by default, to position a popover, all you need to do is set the position.
#anchor{}
#positionedElement {
position-area: end;
margin: unset;
}
Scoping the potential anchors
You may implement anchor positioning as part of a component, so that you can use a pattern like a drop-down menu in multiple places. If you are using the same anchor-name
multiple times, how do you make sure that each positioned element finds the correct anchor?
JavaScript solutions involve adding unique IDs to each anchor, and then referring to that from the positioned element. This gets cumbersome, and CSS has a simpler solution with anchor-scope
.
The anchor-scope
property sets which anchor names will be matched only among an element and its descendants. It accepts a list of one or more anchor names or the keyword all
to limit the scope of all defined anchor names.
An anchor-scope
is ideally added to an ancestor of both the positioned element and anchor element that does not contain other anchor elements with the same name. Often, this is on the reusable component's root.
The following example shows the difference anchor-scope
makes when applied to repeated elements with the same anchor-name
. In the example, all of the <img>
elements and the image banners reference the --image
anchor name. When anchor-scope
is applied to the <li>
elements, position-anchor: --image
will match only the <img>
element within the same <li>
element as the banner, otherwise it will match the last rendered <img>
.
Positioning
Now that you've tethered the element to your anchor, it's time to position it. Anchor positioning provides two methods for positioning – position-area
and the anchor()
function.
position-area
The position-area
property lets you position an element around the anchor by specifying one or two keywords. This covers many common use cases, and is often a good place to start.
How position-area
works
position-area
works by creating a new containing block for the positioned element in an area generated by the edges of the anchor and the positioned element's original containing block.
While there are many keywords available for position-area
, they can be broken down into a few categories to make them more understandable. Anchor-tool.com is a great tool for exploring the syntax.
Physical keywords
You can use the physical keywords, top
, left
, bottom
, right
, and center
. For example, position-area: top right
will place the positioned element above and to the right of the anchor. These keywords also have physical axis equivalents, y-start
, x-start
, y-end
, and x-end
.
Logical keywords
You can also use logical keywords, block-start
, block-end
, inline-start
, and inline-end
. For example, position-area: block-end inline-start
will place the positioned element beneath and to the left of the anchor in languages like English, or after the anchor on the block axis and before the anchor on the inline axis in the document's writing mode. center
can also be used with a logical keyword.
You can also omit the axis if you are specifying logical keywords, with the block axis first and the inline axis second. position-area: start end
is the same as position-area: block-start inline-end
or even position-area: inline-end block-start
.
Spanning multiple grid areas
So far, you may have noticed that these options only allow you to place the positioned element within a single grid space. Adding the span
prefix to physical or logical properties adds the adjacent center grid space. position-area: span-top right
will be positioned to the right of the anchor, and from the anchor's bottom to the top of the positioned element's original containing block.
A common position-area for a drop-down menu is position-area: block-end span-inline-end
.
The span-all
keyword spans 3 rows or columns.
Single keyword
If you only set one keyword, the other axis is set automatically. This largely works as you would expect it to work, but it may be useful to understand how it works.
If the provided keyword is clear about its axis, the other axis is computed as span-all
. This means that position-area: bottom
is equivalent to position-area: bottom span-all
, and the positioned element will be below the anchor, and have the entire width of the containing block available.
On the other hand, if the keyword doesn't clearly indicate an axis, it is repeated. position-area: start
is equivalent to start start
, and is placed at the top left of the anchor in left to right languages.
The anchor()
function
For more advanced use cases, position-area
may not meet your requirements. The anchor()
function lets you set individual inset properties based on the position of another element. This resolves to a CSS length, meaning you can use it in calculations and with other CSS functions. In addition, you can also tether different sides to different anchors.
The anchor()
function takes an anchor name and an anchor side. If your element has a default anchor, either set with position-anchor
or implicitly, for example with a popover, you can omit the anchor name.
.positionedElement {
block-start: anchor(--my-anchor start);
/* OR */
position-anchor: --my-anchor;
block-start: anchor(start);
}
Fallback values
If an anchor can't be found for an anchor()
function, the entire declaration will be invalid. This might happen if the anchor is rendered after the positioned element, or if there isn't an element with matching anchor-name
. To handle this, you can set a fallback length or percentage.
.positionedElement {
block-start: anchor(--my-anchor, 100px)
}
In the preceding example, the positioned element's left value is anchored to --focused-anchor
, but that anchor-name
only exists when the first button is hovered or focused. Because an anchor()
function resolves to a length, you can use another anchor as a fallback. If we didn't provide a fallback, the positioned element wouldn't be positioned.
Anchor side keywords
The anchor side value chooses which of the anchor's edges to position against. Similar to position-area
, the anchor side value supports several different types of syntax.
Type | Values | Description |
---|---|---|
Physical | top , left , bottom , right |
Physical keywords correspond to a specific side of the anchor, but can only be used on the same axis as the positioned element's inset that you are setting. For example, |
Side | inside , outside |
The For example, |
Logical | start , end , self-start , self-end |
Logical keywords refer to the anchor's sides based on the writing mode of the positioned element with |
Percentage | 0% - 100% |
A percentage value places the positioned element along the axis from the anchor's start to end on the specified axis. |
This example shows how a percentage value always goes from the start to the end on the specified axis:
Using anchor()
Because anchor()
is a length, it's very flexible. You can manipulate the value with CSS functions like max()
and calc()
.
One limitation is that you are only able to use anchor()
functions on inset properties.
The preceding example adds a background behind the open details panel that animates smoothly when a different panel is opened, and stretches to include a hovered details panel. To accomplish this, it uses min()
to pick the smaller length between two anchors.
#indicator{
/* Use the smaller of the 2 values: */
inset-block-start: min(
/* 1. The start side of the default anchor, which is the open `<details>` element */
anchor(start),
/* 2. The start side of the hovered `<details>` element. */
anchor(--hovered start,
/* If no `<details>` element is hovered, this falls back to infinity px, so that the other value is smaller, and therefore used. */
var(calc(1px * infinity)))
);
}
The example also uses calc()
to add inline space around the open panel.
Using the anchor's size
You can also use the anchor-size()
function to use the anchor's dimensions for your positioned element's size, position or margin.
anchor-size()
takes an anchor name, or uses the default anchor. By default, it will use the size of the anchor on the axis that it is being used, so width: anchor-size()
will return the anchor's width. You can also use the other axis by specifying which length you want, with the physical keywords width
and height
or the logical keywords block
, inline
, self-block
and self-inline
.
Handling overflow
You've made a drop-down menu component, and used anchor positioning to place the drop-down menu where you want it to go. But then you move the menu over to the other side of the screen, or use it for a user menu, and the user's name is extra long. Suddenly, your drop-down is off the screen. Now what?
CSS anchor positioning has a built-in system that allows you quickly build a robust set of fallbacks when your positioned element ends up outside of its containing block.
Fallback options
The position-try-fallbacks
rule takes a list of fallback options. When the default position overflows, each option will be tried in order until there is a position that doesn't overflow.
You can use any position-area
value as a fallback option. In this example, in left-to-right writing modes like English, the positioned element will try to be positioned at the bottom of the anchor, spanning the center and right columns. If that overflows, it will try to be positioned at the bottom of the anchor, spanning the left and center columns. If that overflows as well, the position will revert back to the default position, even though that overflows.
.positioned-element {
position-area: block-end span-inline-end;
position-try-fallbacks: block-end span-inline-start;
}
There are also several flip-
keywords that handle common fallback cases. flip-block
and flip-inline
try flipping the element over the block and inline axes. They can also be combined with flip-block flip-inline
to flip over both axes. The flip-start
value flips the positioned element over a diagonal line from the start to the end corners of the anchor.
You can also create a custom fallback option with @position-try
—this lets you set the margins, alignment, and even change the anchor.
@position-try --menu-below {
position-area: bottom span-right;
margin-top: 1em;
}
#positioned-element {
position-try: --menu-below;
}
flip-block
and flip-inline
can be added to @position-try
fallback options to create a variant.
#positioned-element {
position-try: --menu-below, flip-inline --menu-below;
}
In the preceding example, the browser follows these steps, stopping as soon as it finds a solution that doesn't overflow.
- The element is placed with
position-area: end
, at the bottom right of the anchor. - If that overflows, the element is placed with the custom fallback option named
--bottom-span-right
, which places it withposition-area: bottom span-right
, with an additional margin beneath. - If that overflows, the element is placed with
flip-inline --bottom-span-right
, which combines the custom fallback option withflip-inline
, which is essentiallyposition-area: bottom span-left
. - If that overflows, the element is placed using the
--use-alternate
custom fallback option, which places it below a completely different anchor. - If that overflows, the element reverts to its original placement, with
position-area: end
, even though that is known to overflow.
Fallback order
By default, when the initial position overflows, the browser will try each option in position-try-fallbacks
until a position is found that doesn't overflow. You can override this behavior with position-try-order
to test each fallback option, and use the one that has the most space on a specified axis.
You can specify the axis with either the logical keywords, most-block-size
and most-inline-size
, or with the physical keywords most-height
and most-width
.
position-try-order
and position-try-fallbacks
can be combined with the position-try
shorthand, with the order coming first.
Scrolling
When a user scrolls, they expect the page to move fluidly. To accomplish this, browsers have limits on how anchor positioning can be used when scrolling.
While you can tether a positioned element to anchors in different scroll containers, the element will only move in response to one of the anchors scrolling. This will be the default anchor, which is either the implicit anchor from a popover, or the value of position-anchor
.
You'll note that the positioned element stays visible even as the anchor is scrolled out of view. To hide the positioned element when the anchor is hidden, set position-visibility: anchors-visible
. This not only applies to when the anchor is overscrolled, but also if it is hidden in other ways, for example with visibility: hidden
.
Check your understanding
Which are valid values for the side in anchor()
?
inside
25%
25px
25px
can be used as the fallback value, only percentages can be used for the side.block-start
start
Which are valid values for position-area
?
top
block-end inline-end
block-start block-end
Which properties support the anchor()
function?
top
margin-left
inset-block-start
transform
If there are multiple anchors with the same anchor-name
, what happens?