1. Introduction
CSS absolute positioning allows authors to place boxes anywhere on the page, without regard to the layout of other boxes besides their containing block. This flexibility can be very useful, but also very limiting—often you want to position relative to some other box. Anchor positioning (via the position-anchor and position-area properties and/or the anchor functions anchor() and anchor-size()) allows authors to achieve this, “anchoring” an absolutely positioned box to one or more other boxes on the page, while also allowing them to try several possible positions to find the “best” one that avoids overlap/overflow.
.anchor{ anchor-name : --tooltip; } .tooltip{ /* Fixpos means we don’t need to worry about containing block relationships; the tooltip can live anywhere in the DOM. */ position: fixed; /* All the anchoring behavior will default to referring to the --tooltip anchor. */ position-anchor: --tooltip; /* Align the tooltip’s bottom to the top of the anchor; this also defaults to horizontally center-aligning the tooltip and the anchor (in horizontal writing modes). */ position-area: block-start; /* Automatically swap if this overflows the window so the tooltip’s top aligns to the anchor’s bottom instead. */ position-try: flip-block; /* Prevent getting too wide */ max-inline-size:20 em ; }
1.1. Value Definitions
This specification follows the CSS property definition conventions from [CSS2] using the value definition syntax from [CSS-VALUES-3]. Value types not defined in this specification are defined in CSS Values & Units [CSS-VALUES-3]. Combination with other CSS modules may expand the definitions of these value types.
In addition to the property-specific values listed in their definitions, all properties defined in this specification also accept the CSS-wide keywords as their property value. For readability they have not been repeated explicitly.
Like most operations in CSS besides selector matching, features in this specification operate over the flattened element tree.
2. Determining the Anchor
2.1. Creating an Anchor: the anchor-name property
Name: | anchor-name |
---|---|
Value: | none | <dashed-ident># |
Initial: | none |
Applies to: | all elements that generate a principal box |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | discrete |
The anchor-name property declares that an element is an anchor element, whose principal box is an anchor box, and gives it a list of anchor names to be targeted by. Values are defined as follows:
- none
-
The property has no effect.
- <dashed-ident>#
-
If the element generates a principal box, the element is an anchor element, with a list of anchor names as specified. Each anchor name is a tree-scoped name.
Otherwise, the property has no effect.
Anchor names do not need to be unique. Not all elements are capable of being the target anchor element of a given box. Thus a name can be reused in multiple places if the usages are scoped appropriately.
Note: If multiple elements share an anchor name and are all visible to a given positioned box, the target anchor element will be the last one in DOM order. The anchor-scope property can be used to further limit what names are visible to a given referencing box.
Anchor names are not scoped by containment by default; even if an element has style or layout containment (or any similar sort of containment), the anchor names of its descendants are visible to elements elsewhere in the page.
Note: While an element is in the skipped contents of another element (due to content-visibility: hidden, for instance), it’s not an acceptable anchor element, effectively acting as if it had no names.
2.1.1. Implicit Anchor Elements
Some specifications can define that, in certain circumstances, a particular element is an implicit anchor element for another element.
TODO: Fill in an example new popover-related details (once that finally lands in the HTML spec).
Implicit anchor elements can be referenced with the auto keyword in position-anchor, or by omitting the anchor reference in anchor functions.
Pseudo-elements have the same implicit anchor element as their originating element, unless otherwise specified.
Note: Without this, these pseudo-elements, which are often inaccessible by other specifications, cannot be positioned with implicit anchor elements.
2.1.2. The Anchor’s Position
Several features of this specification refer to the position and size of an anchor box. The anchor box's position and size is determined after layout, and for these purposes includes position-based adjustments (such as position: relative or position: sticky).
Post-layout effects, such as transform, do not affect the anchor box’s position.
Note: Allowing an anchor to opt into including the effects of transform or similar properties might be allowed in the future.
2.2. Scoping Anchor Names: the anchor-scope property
Name: | anchor-scope |
---|---|
Value: | none | all | <dashed-ident># |
Initial: | none |
Applies to: | all elements |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | discrete |
This property scopes the specified anchor names, and lookups for these anchor names, to this element’s subtree. See § 2 Determining the Anchor.
Values have the following meanings:
- none
- No changes in anchor name scope.
- all
- Specifies that all anchor names defined by this element or its descendants—whose scope is not already limited by a descendant using anchor-scope—to be in scope only for this element’s descendants; and limits descendants to only match anchor names to anchor elements within this subtree.
- <dashed-ident>
- Specifies that a matching anchor name defined by this element or its descendants—whose scope is not already limited by a descendant using anchor-scope—to be in scope only for this element’s descendants; and limits descendants to only match these anchor names to anchor elements within this subtree.
This property has no effect on implicit anchor elements.
li{ anchor-name : --list-item; anchor-scope : --list-item; } li .positioned{ position : absolute; position-anchor : --list-item; position-area : inline-start; }
Without anchor-scope,
all of the li
elements would be visible
to all of the positioned elements,
and so they’d all positioned themselves relative to the final li
,
stacking up on top of each other.
2.3. Finding an Anchor
Several things in this specification find a target anchor element, given an anchor specifier, which is either a <dashed-ident> (and a tree-scoped reference) that should match an anchor-name value elsewhere on the page, or the keyword auto, or nothing (a missing specifier).
Note: The general rule captured by these conditions is that an element can only be a positioned box’s target anchor element if its own box is fully laid out before the positioned box that wants to reference it is laid out. CSS’s layout rules provide some useful guarantees about this, depending on the anchor and positioned box’s relationship with each other and their containing blocks. The list of conditions below exactly rephrases the stacking context rules into just what’s relevant for this purpose, ensuring there is no possibly circularity in anchor positioning.
-
If anchor spec was not passed, return the default anchor element if it exists, otherwise return nothing.
-
If anchor spec is auto:
-
If the Popover API defines an implicit anchor element for query el which is an acceptable anchor element for query el, return that element.
-
Otherwise, return nothing.
Note: Future APIs might also define implicit anchor elements. When they do, they’ll be explicitly handled in this algorithm, to ensure coordination.
-
-
Otherwise, anchor spec is a <dashed-ident>. Return the last element el in tree order that satisfies the following conditions:
-
el is an anchor element with an anchor name of anchor spec.
-
el’s anchor name and anchor spec are both associated with the same tree root.
Note: The anchor name is a tree-scoped name, while anchor spec is a tree-scoped reference.
-
el is an acceptable anchor element for query el.
If no element satisfies these conditions, return nothing.
Note: anchor-scope can restrict the visibility of certain anchor names, which can affect what elements can be anchor elements for a given lookup.
-
Note: An anchor-name defined by styles in one shadow tree won’t be seen by anchor functions in styles in a different shadow tree, preserving encapsulation. However, elements in different shadow trees can still anchor to each other, so long as both the anchor-name and anchor function come from styles in the same tree, such as by using ::part() to style an element inside a shadow. (Implicit anchor elements also aren’t intrinsically limited to a single tree, but the details of that will depend on the API assigning them.)
-
possible anchor is either an element or a fully styleable tree-abiding pseudo-element.
-
possible anchor is in scope for positioned el, per the effects of anchor-scope on positioned el or its ancestors.
-
possible anchor is painted strictly before positioned el, aka one of the following is true:
-
positioned el is in a higher top layer than possible anchor
-
Both elements are in the same top layer but have different containing blocks, and positioned el’s containing block is an ancestor of possible anchor’s containing block in the containing block chain, aka one of the following:
-
positioned el’s containing block is the viewport, and possible anchor’s containing block isn’t.
-
positioned el’s containing block is the initial containing block, and possible anchor’s containing block is generated by an element,
-
both elements' containing blocks are generated by elements, and positioned el’s containing block is an ancestor in the flat tree to that of possible anchor’s containing block.
-
-
Both elements are in the same top layer and have the same containing block, and are both absolutely positioned, and possible anchor is earlier in flat tree order than positioned el.
-
Both elements are in the same top layer and have the same containing block, but possible anchor isn’t absolutely positioned.
-
-
If possible anchor is in the skipped contents of another element, then positioned el is in the skipped contents of that same element.
Note: In other words, positioned el can anchor to possible anchor if they’re both in the same skipped "leaf", but it can’t anchor "across" leafs. This means skipping an element that contains both of them won’t suddenly cause the positioned el to move to another anchor, but still prevents positioned elements elsewhere in the page from anchoring to the skipped element.
2.4. Default Anchors: the position-anchor property
Name: | position-anchor |
---|---|
Value: | auto | <anchor-name> |
Initial: | auto |
Applies to: | absolutely positioned boxes |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | discrete |
The position-anchor property specifies the default anchor element, which is used by position-area, position-try, and (by default) all anchor functions applied to this element. position-anchor is a reset-only sub-property of position.
- auto
-
Use the implicit anchor element if it exists; otherwise the box has no default anchor element.
- <anchor-name>
-
The target anchor element selected by the specified <anchor-name> is the box’s default anchor element.
The principal box of the default anchor element is the box’s default anchor box.
.anchored{ position : absolute; top : calc ( .5 em +anchor ( outside)); /* Since no anchor name was specified, this automatically refers to the default anchor box. */ } .foo.anchored{ position-anchor : --foo; } .bar.anchored{ position-anchor : --bar; }
2.5. Anchor Relevance
When determining whether an element el is relevant to the user, if a descendant of el is a target anchor element for a positioned box (which itself is not skipped and whose containing block is not el or a descendant of el), then el must be considered relevant to the user.
Note: This means that, for example, an anchor in a content-visibility: auto subtree will prevent its subtree from skipping its contents as long as the positioned box relying on it is also not skipped. (Unless the anchor and the positioned box are both under the same content-visibility: auto element; they can’t cyclicly keep each other visible.)
3. Anchor-Based Positioning
An absolutely positioned box can position itself relative to one or more anchor boxes on the page.
The position-area property offers a convenient grid-based concept for positioning relative to the default anchor box; for more complex positioning or positioning relative to multiple boxes, the anchor() function can be used in the inset properties to explicitly refer to edges of an anchor box.
3.1. The position-area Property
Name: | position-area |
---|---|
Value: | none | <position-area> |
Initial: | none |
Applies to: | positioned boxes with a default anchor box |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | TBD |
Most common use-cases of anchor positioning are only concerned with the edges of the positioned box’s containing block and the edges of the default anchor box. These lines can be thought of as defining a 3×3 grid; position-area lets you easily specify what area of this position-area grid to lay out the positioned box in. Its syntax is:
<position-area> = [ [ left | center | right | span-left | span-right | x-start | x-end | span-x-start | span-x-end | x-self-start | x-self-end | span-x-self-start | span-x-self-end | span-all ] || [ top | center | bottom | span-top | span-bottom | y-start | y-end | span-y-start | span-y-end | y-self-start | y-self-end | span-y-self-start | span-y-self-end | span-all ] | [ block-start | center | block-end | span-block-start | span-block-end | span-all ] || [ inline-start | center | inline-end | span-inline-start | span-inline-end | span-all ] | [ self-block-start | center | self-block-end | span-self-block-start | span-self-block-end | span-all ] || [ self-inline-start | center | self-inline-end | span-self-inline-start | span-self-inline-end | span-all ] | [ start | center | end | span-start | span-end | span-all ]{1,2} | [ self-start | center | self-end | span-self-start | span-self-end | span-all ]{1,2} ]
- none
-
The property has no effect.
- <position-area>
-
If the box does not have a default anchor box, or is not an absolutely positioned box, this value has no effect.
Otherwise, it has the following effects:
-
The property selects a region of the position-area grid, and makes that the box’s containing block. See § 3.1.1 Resolving <position-area>s for details.
Note: This means that the inset properties specify offsets from the position-area, and some property values, like max-height: 100%, will be relative to the position-area as well.
-
The normal value for the self-alignment properties behaves as either start, end, or anchor-center, depending on the positioning of the region, to give a good default alignment for the positioned box. Again, see § 3.1.1 Resolving <position-area>s for details.
-
Any auto inset properties resolve to 0.
-
The box is not considered to be in a legacy alignment mode in either axis.
-
This behavior makes it more likely that positioned boxes remain visible and within their intended bounds, even when their containing block ends up smaller than anticipated.
For example, a position-area: bottom span-right value lets the positioned box stretch from its anchor’s left edge to its containing block’s right edge, and left-aligns it in that space by default. But if the positioned box is larger than that space (such as if the anchor is very close to the right edge of the screen), it will shift leftwards to stay visible.
3.1.1. Resolving <position-area>s
The position-area grid is a 3×3 grid, composed of four grid lines in each axis. In order (and using the writing mode of the containing block):
-
the start edge of the box’s pre-modification containing block, or the start edge of the default anchor box if that is more start-ward
-
the start edge of the default anchor box
-
the end edge of the default anchor box
-
the end edge of the box’s pre-modification containing block, or the end edge of the default anchor box if that is more end-ward.
A <position-area> selects a region of this grid by specifying the rows and columns the region occupies, with each of the two keywords specifying one of them:
- start, end, self-start, self-end
- top, bottom, left, right
- y-start, y-end, y-self-start, y-self-end
- x-start, x-end, x-self-start, x-self-end
- block-start, block-end, block-self-start, block-self-end
- inline-start, inline-end, inline-self-start, inline-self-end
- center
- top, bottom, left, right
-
The single corresponding row or column, depending on which axis this keyword is specifying.
Like in anchor(), the plain logical keywords (start, end, etc) refer to the writing mode of the box’s containing block. The x-start/etc determine their direction in the same way, but in the specified physical axis.
The self-* logical keywords (self-start, x-self-end, etc) are identical, but refer to the box’s own writing mode.
- span-start, span-end
- span-top, span-bottom
- span-y-start, span-y-end
- span-x-start, span-x-end
- span-block-start, span-block-end
- span-inline-start, span-inline-end
- span-top, span-bottom
-
Two rows or columns, depending on which axis this keyword is specifying: the center row/column, and the row/column corresponding to the other half of the keyword as per the single-track keywords.
(For example, span-top spans the first two rows—the center row and the top row.)
- span-all
-
All three rows or columns, depending on which axis this keyword is specifying.
Some keywords are ambiguous about what axis they refer to: center, span-all, and the start/etc keywords that don’t specify the block or inline axis explicitly. If the other keyword is unambiguous about its axis, then the ambiguous keyword is referring to the opposite axis. (For example, in block-start center, the center keyword is referring to the inline axis.) If both keywords are ambiguous, however, then the first refers to the block axis of the box’s containing block, and the second to the inline axis. (For example, span-all start is equivalent to span-all inline-start.)
If only a single keyword is given, it behaves as if the second keyword is span-all if the given keyword is unambigous about its axis; otherwise, it behaves as if the given keyword was repeated. (For example, top is equivalent to top span-all, but center is equivalent to center center.)
The <position-area> also implies a default self-alignment, which will be used if the self-alignment property on the box is normal:
-
If the only the center region in an axis is selected, or all three regions are selected, the default alignment in that axis is anchor-center.
-
Otherwise, the default alignment in that axis is toward the non-specified side region: if it’s specifying the "start" region of its axis, the default alignment in that axis is end; etc.
Note: When the default anchor box is partially or completely outside of the pre-modified containing block, some of the position-area grid’s rows or columns can be zero-sized.
3.2. The anchor() Function
An absolutely positioned box can use the anchor() function as a value in its inset properties to refer to the position of one or more anchor boxes. The anchor() function resolves to a <length>. It is only valid to be used in the inset properties.
Name: | top, left, right, bottom |
---|---|
New values: | <anchor()> |
<anchor()> = anchor( <anchor-name>? && <anchor-side>, <length-percentage>? ) <anchor-name> = <dashed-ident> <anchor-side> = inside | outside | top | left | right | bottom | start | end | self-start | self-end | <percentage> | center
The anchor() function has three arguments:
-
the <anchor-name> value specifies how to find the anchor element it will be drawing positioning information from. Its possible values are:
- <dashed-ident>
-
Specifies the anchor name it will look for. This name is a tree-scoped reference.
- omitted
-
Selects the default anchor element defined for the box, if possible.
See target anchor element for details.
-
the <anchor-side> value refers to the position of the corresponding side of the target anchor element. Its possible values are:
- inside
- outside
-
Resolves to one of the anchor box’s sides, depending on which inset property it’s used in. inside refers to the same side as the inset property (attaching the positioned box to the "inside" of the anchor box), while outside refers to the opposite.
- top
- right
- bottom
- left
- right
-
Refers to the specified side of the anchor box.
Note: These are only usable in the inset properties in the matching axis. For example, left is usable in left, right, or the logical inset properties that refer to the horizontal axis.
- start
- end
- self-start
- self-end
- end
-
Refers to one of the sides of the anchor box in the same axis as the inset property it’s used in, by resolving the keyword against the writing mode of either the positioned box (for self-start and self-end) or the positioned box’s containing block (for start and end).
- <percentage>
- center
-
Refers to a position a corresponding percentage between the start and end sides, with 0% being equivalent to start and 100% being equivalent to end.
center is equivalent to 50%.
- inside
-
the optional <length-percentage> final argument is a fallback value, specifying what the function should resolve to if it’s an invalid anchor function.
An anchor() function representing a valid anchor function resolves at computed value time (using style & layout interleaving) to the <length> that would align the edge of the positioned boxes' inset-modified containing block corresponding to the property the function appears in with the specified border edge of the target anchor element, assuming that all scroll containers between the target anchor element and the positioned box’s containing block are scrolled to their initial scroll position (but see § 3.4 Taking Scroll Into Account).
Note: This means that transitions or animations of a property using an anchor function will work "as expected" for all sorts of possible changes: the anchor box moving, anchor elements being added or removed from the document, the anchor-name property being changed on anchors, etc.
If the target anchor element is fragmented, the axis-aligned bounding rectangle of the fragments' border boxes is used instead.
The positioned box is additionally visually shifted by its snapshotted scroll offset, as if by an additional translate() transform.
.bar
element’s block-start edge
with the --foo anchor’s block-start edge.
On the other hand,
in .bar { inset-block-end: anchor(--foo block-start); },
it will instead resolve to the length
that’ll line up the .bar
element’s block-end edge
with the --foo anchor’s block-start edge.
Since inset-block-start and inset-block-end values specify insets from different edges (the block-start and block-end of the element’s containing block, respectively), the same anchor() will usually resolve to different lengths in each.
For example, the following will set up the element so that its inset-modified containing block is centered on the anchor box and as wide as possible without overflowing the containing block:
.centered-message{ position : fixed; max-width : max-content; justify-self : center; --center : anchor ( --x50 % ); --half-distance : min ( abs ( 0 % -var ( --center)), abs ( 100 % -var ( --center)) ); left : calc ( var ( --center) -var ( --half-distance)); right : calc ( var ( --center) -var ( --half-distance)); bottom : anchor ( --x top); }
This might be appropriate for an error message
on an input
element,
for example,
as the centering will make it easier to discover
which input is being referred to.
3.3. Centering on the Anchor: the anchor-center value
Name: | justify-self, align-self, justify-items, align-items |
---|---|
New values: | anchor-center |
The self-alignment properties allow an absolutely positioned box to align itself within the inset-modified containing block. The existing values, plus carefully chosen inset properties, are usually enough for useful alignment, but a common case for anchored positioning—centering over the anchor box—requires careful and somewhat complex set-up to achieve.
The new anchor-center value makes this case extremely simple: if the positioned box has a default anchor box, then it is centered (insofar as possible) over the default anchor box in the relevant axis.
Additionally, any auto inset properties resolve to 0.
If the box is not absolutely positioned, or does not have a default anchor box, this value behaves as center and has no additional effect on how inset properties resolve.
Note: Similar to position-area, when using anchor-center, if the anchor is too close to the edge of the box’s original containing block, by default it will “shift” from being purely centered, in order to remain within the original containing block. See CSS Box Alignment 3 § 4.4 Overflow Alignment: the safe and unsafe keywords and scroll safety limits for more details.
3.4. Taking Scroll Into Account
Because scrolling is often done in a separate thread from layout in implementations for performance reasons, but anchor() can result in both positioning changes (which can be handled in the scrolling thread) and layout changes (which cannot), anchor() is defined to assume all the scroll containers between the anchor box and the positioned box’s containing block are at their initial scroll position. This means a positioned box will not be aligned with its anchor if any of the scrollers are not at their initial positions.
To compensate for this without losing the performance benefits of the separate scrolling thread, we define:
-
abspos has a default anchor box.
-
abspos’s used self-alignment property value in that axis is anchor-center; or at least one anchor() function on abspos’s used inset properties in the axis refers to a target anchor element with the same nearest scroll container ancestor as abspos’s default anchor box.
Note: If abspos has a position options list, then whether it needs scroll adjustment in an axis is also affected by the applied fallback style.
abspos’s snapshotted scroll offset is a pair of lengths for the horizontal and vertical axises, respectively. Each length is calculated as:
-
If abspos needs scroll adjustment in the axis, then the length is the sum of the scroll offsets of all scroll container ancestors of the default anchor box in the same axis, up to but not including abspos’s containing block;
-
Otherwise, the length is 0.
If the default anchor box, or ancestors between it and abspos’s containing block, are subject to additional scroll adjustments, such as from further anchor positioning, or sticky positioning, etc., add that adjustment to the snapshotted scroll offset as well.
Define the precise timing of the snapshot: updated each frame, before style recalc.
Implied restrictions of scroll adjustment
In short: a positioned box is able to have its position rely on a scrollable anchor in a different scroll container, but cannot have its size depend on that. This restriction allows "composited scrolling" to continue to be used, where the actual shifting of a scrollable box’s contents is done in a separate "compositing thread" in the UA, and the rest of the CSS (in particular, layout) can’t directly depend on its results.
This is why only one anchor (the default anchor box) is used for scroll adjustment—if multiple anchors could be adjusted for, then a positioned box could use different ones for opposite sides, and trigger layout as a result of either of them scrolling.
Position fallback *can* end up depending on scroll adjustment, which can cause re-layout since the sizing properties are accepted @position-try properties. However, it’s acceptable for fallback due to scrolling to happen a frame late, after the UA has had time to receive scrolling information from the compositing thread. This is not the case for simple position tracking—if the positioned box is meant to sit snug against the anchor, having it lag a frame behind is very user-visible, and a pretty bad experience.
If you want to have a positioned box’s size depend on an anchor’s scroll position, the positioned box must be inside of the same scroll container, so the two boxes are never scrolled relative to each other at all.
3.5. Validity
An anchor() function is a valid anchor function only if all the following conditions are true:
-
It’s being used in an inset property on an absolutely positioned box.
-
If its <anchor-side> specifies a physical keyword, it’s being used in an inset property in that axis. (For example, left can only be used in left, right, or a logical inset property in the horizontal axis.)
-
The result of determining the target anchor element is not nothing when given the querying element as the element it’s used on, and the anchor specifier as the <anchor-name> value specified in the function.
If any of these conditions are false, the anchor() function resolves to its specified fallback value. If no fallback value is specified, it makes the declaration referencing it invalid at computed-value time.
3.6. Conditional Hiding: the position-visibility property
Name: | position-visibility |
---|---|
Value: | always | [ anchors-valid || anchors-visible || no-overflow ] |
Initial: | anchors-visible |
Applies to: | absolutely positioned boxes |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | discrete |
There are some conditions in which it might not make sense to display an absolutely positioned box. This property allows such boxes to be made conditionally visible, depending on some commonly needed layout conditions.
- always
-
This property has no effect. (The box is displayed without regard for its anchors or its overflowing status.)
- anchors-valid
-
If any of the box’s required anchor references do not resolve to a target anchor element, the box is .
What is a required anchor reference? anchor() functions that don’t have a fallback value; the default anchor *sometimes*? Need more detail here.
Any anchors are missing, or all anchors are missing? I can see use-cases for either, potentially. Do we want to make a decision here, or make it controllable somehow?
- anchors-visible
-
If the box has a default anchor box but that anchor box is clipped by intervening boxes, this box is also .
- no-overflow
-
If the box overflows its inset-modified containing block even after applying position-try, then the box is .
Note: This means that if an abspos is next to its anchor in the DOM, for example, it’ll remain visible even if its default anchor is scrolled off, since it’s clipped by the same scroller anyway.
Make sure this definition of clipped is consistent with View Transitions, which wants a similar concept.
anchor is also considered clipped by intervening boxes if it is by position-visibility.
Note: This ensures that in a "chained anchor" situation, if the first abspos is hidden due to this property (due to its anchor being scrolled off), then another abspos using it as an anchor will also be hidden, rather than also floating in a nonsensical location.
A box that is strongly hidden acts as if it and all of its descendants were visibility: hidden, regardless of what their actual visibility value is.
3.7. Accessibility Implications
It’s important to remember that Anchor Positioning does not automatically establish any semantic relationship between a positioned box and any of its anchors, because it can be used in many different ways. Authors must not rely solely on a visual connection implied by the positioning to link elements together semantically; without additional help, the elements often have no meaningful DOM relationship, making them difficult or impossible to use in non-visual user agents, like screen readers.
Many features on the web platform, both existing and upcoming, allow establishing such connections explicitly, so that non-visual user agents can also benefit.
For example, the Popover API in HTML automatically links the invoker button to the popover element, including automatically adjusting tabbing order; it also establishes the invoker button as the implicit anchor element for the popover, making it easy to use Anchor Positioning as well.
In more general cases,
ARIA features such as
the aria-details
or aria-describedby
attributes
on an anchor element
can provide this information
in a slightly more manual fashion;
in concert with the role
attribute on the positioned element,
non-visual user agents
can tell their users about the relationship between the elements
and let them automatically navigate between them.
4. Anchor-Based Sizing
An absolutely positioned box can use the anchor-size() function in its sizing properties to refer to the size of one or more anchor boxes. The anchor-size() function resolves to a <length>. It is only valid to be used in the accepted @position-try properties.
4.1. The anchor-size() Function
Name: | width, height, min-width, min-height, max-width, max-height, top, left, right, bottom, margin-top, margin-left, margin-right, margin-bottom |
---|---|
New values: | <anchor-size()> |
anchor-size() = anchor-size( [ <anchor-name> || <anchor-size> ]? , <length-percentage>? ) <anchor-size> = width | height | block | inline | self-block | self-inline
The anchor-size() function is similar to anchor(), and takes the same arguments, save that the <anchor-side> keywords are replaced with <anchor-size>, referring to the distance between two opposing sides.
The physical <anchor-size> keywords (width and height) refer to the width and height, respectively, of the target anchor element. Unlike anchor(), there is no restriction on having to match axises; for example, width: anchor-size(--foo height); is valid.
The logical <anchor-size> keywords (block, inline, self-block, and self-inline) map to one of the physical keywords according to either the writing mode of the box (for self-block and self-inline) or the writing mode of the box’s containing block (for block and inline).
If the <anchor-size> keyword is omitted, it defaults to behaving as whatever keyword matches the axis of the property that anchor-size() is used in. (For example, width: anchor-size() is equivalent to width: anchor-size(width).)
An anchor-size() function representing a valid anchor-size function resolves at computed value time (via style & layout interleaving) to the <length> separating the relevant border edges (either left and right, or top and bottom, whichever is in the specified axis) of the target anchor element.
4.2. Validity
An anchor-size() function is a valid anchor-size function only if all the following conditions are true:
-
It’s being used in a sizing property, an inset property, or a margin property on an absolutely positioned box. (These are the accepted @position-try properties that allow lengths.)
-
There is a target anchor element for the box it’s used on, and the <anchor-name> value specified in the function.
If any of these conditions are false, the anchor-size() function resolves to its specified fallback value. If no fallback value is specified, it makes the declaration referencing it invalid at computed-value time.
5. Overflow Management
Anchor positioning, while powerful, can also be unpredictable. The anchor box might be anywhere on the page, so positioning a box in any particular fashion (such as above the anchor, or the right of the anchor) might result in the positioned box overflowing its containing block or being positioned partially off screen.
To ameliorate this, an absolutely positioned box can use the position-try-fallbacks property to refer to several variant sets of positioning/alignment properties (generated from the box’s existing styles, or specified in @position-try rules) that the UA can try if the box overflows its initial position. Each is applied to the box, one by one, and the first that doesn’t cause the box to overflow its containing block is taken as the winner.
position-try-order allows these options to additional be sorted by the available space they define, if it’s more important for the box to have as much space as possible rather than strictly follow some declared order.
5.1. Giving Fallback Options: the position-try-fallbacks property
Name: | position-try-fallbacks |
---|---|
Value: | none | [ [<dashed-ident> || <try-tactic>] | <'position-area'> ]# |
Initial: | none |
Applies to: | absolutely positioned boxes |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | discrete |
This property provides a list of alternate positioning styles to try when the absolutely positioned box overflows its inset-modified containing block. This position options list is initially empty.
Each comma-separated entry in the list is a separate option: either the name of a @position-try block, or a <try-tactic> representing an automatic transformation of the box’s existing computed style.
Values have the following meanings:
- none
-
The property has no effect; the box’s position options list is empty.
- <dashed-ident>
-
If there is a @position-try rule with the given name, its associated position option is added to the position options list.
Otherwise, this value has no effect.
- <try-tactic>
-
Automatically creates a position option from the box’s computed style, by swapping due to a try-tactic according to the specified keyword, and adding the constructed position option to the box’s position options list. and adds it to the position options list.
<try-tactic> = flip-block || flip-inline || flip-start
- flip-block
-
swaps the values in the block axis (between, for example, margin-block-start and margin-block-end), essentially mirroring across an inline-axis line.
- flip-inline
-
swaps the values in the inline axis, essentially mirroring across a block-axis line.
- flip-start
-
swaps the values of the start properties with each other, and the end properties with each other (between, for example, margin-block-start and margin-inline-start), essentially mirroring across a diagonal drawn from the start-start corner to the end-end corner.
If multiple keywords are given, the transformations are composed in order to produce a single position option.
- <dashed-ident> || <try-tactic>
-
Combines the effects of the previous two options: if there is a @position-try rule with the given name, applies its position option to the base style, then transforms it according to the specified <try-tactic> and adds the result to the box’s position options list.
Otherwise, does nothing.
- <'position-area'>
-
Automatically creates a position option composed solely of a position-area property with the given value.
-
If directions are opposites along the same axis, they are “opposing”. Otherwise (when they are specifying different axises), they are “perpendicular”.
-
Determine the specified values of the accepted @position-try properties on el, and let styles be the result.
-
Substitute variables, env() functions, and similar arbitrary substitution functions in styles.
For env() functions, if the referenced environment variable is associated with a direction or axis (such as safe-area-inset-top), switch the referenced environment variable corresponding to directions.
For example, if top: env(safe-area-inset-top); is specified, and directions are up and left, the env() will resolve as if env(safe-area-inset-left) had been specified instead. (And then, in the next step, will actually swap into the left property.) -
Swap the values of the styles between the associated properties corresponding to directions.
For example, if "top" and "left" are being swapped, then the values of margin-top and margin-left are swapped, width and height are swapped, etc.Note: If the directions are opposites along the same axis, some properties (like width or align-self) wont' swap, since they’re associated with themselves across the two directions, but their values might be changed by the next step.
-
Modify the values of the properties as they swap to match the new directions, as follows:
-
For inset properties, change the specified side in anchor() functions to maintain the same relative relationship to the new direction that they had to the old.
If a <percentage> is used, and directions are opposing, change it to 100% minus the original percentage.
For example, if "top" and "left" are being swapped, then margin-top: anchor(bottom) will become margin-left: anchor(right).If "top" and "bottom" are being swapped, then margin-top: anchor(20%) will become margin-bottom: anchor(80%).
-
For sizing properties, change the specified axis in anchor-size() functions to maintain the same relative relationship to the new direction that they had to the old.
For example, if "top" and "left" are being swapped, then width: anchor-size(width) will become height: anchor-size(height). -
For the self-alignment properties, if directions are opposing, change the specified <self-position> (or left/right keywords), if any, to maintain the same relative relationship to the new direction that they had to the old.
For example, if "top" and "bottom" are being swapped, then align-self: start will become align-self: end.However, align-self: center will remain unchanged, as it has the same relationship to both directions.
Similarly, align-self: first baseline will remain unchanged, as it’s a <baseline-position> rather than a <self-position>.
-
For position-area, change the value so that the selected rows/columns of the position-area grid maintain the same relative relationship to the new direction that they had to the old.
For example, if "top" and "left" are being swapped, then position-area: left span-bottom will become position-area: top span-right.
-
-
Return styles.
5.2. Determining Fallback Order: the position-try-order property
Name: | position-try-order |
---|---|
Value: | normal | <try-size> |
Initial: | normal |
Applies to: | absolutely positioned boxes |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | discrete |
This property specifies the order in which the position options list will be tried.
<try-size> = most-width | most-height | most-block-size | most-inline-size
- normal
-
Try the position options in the order specified by position-try-fallbacks.
- most-width
- most-height
- most-block-size
- most-inline-size
- most-height
-
For each entry in the position options list, apply that position option to the box, and find the specified inset-modified containing block size that results from those styles. Stably sort the position options list according to this size, with the largest coming first.
.anchor{ anchor-name : --foo; } .list{ position : fixed; position-anchor : --foo; position-area : block-end span-inline-end; align-self : start; position-try-fallbacks : --bottom-scrollable, flip-block, --top-scrollable; position-try-order : most-height; } @position-try --bottom-scrollable{ align-self : stretch; } @position-try --top-scrollable{ position-area : block-start span-inline-end; align-self : stretch; }
The flip-block auto-generated option and the --top-scrollable option will always find the same available height, since both of them stretch vertically from the top edge of the containing block to the top of the anchor, so they’ll retain their specified order. This causes the box to first try to align against the anchor at its natural height (using align-self: end, auto-reversed from the base styles), but if that also causes overflow, it’ll fall back to just filling the space and being scrollable instead.
5.3. The position-try Shorthand
Name: | position-try |
---|---|
Value: | <'position-try-order'>? <'position-try-fallbacks'> |
Initial: | see individual properties |
Applies to: | see individual properties |
Inherited: | see individual properties |
Percentages: | see individual properties |
Computed value: | see individual properties |
Animation type: | see individual properties |
Canonical order: | per grammar |
This shorthand sets both position-try-fallbacks and position-try-order. If <'position-try-order'> is omitted, it’s set to the property’s initial value.
5.4. The @position-try Rule
The @position-try rule defines a position option with a given name, specifying one or more sets of positioning properties that can be applied to a box via position-try-fallbacks,
The syntax of the @position-try rule is:
@position-try <dashed-ident> { <declaration-list> }
The <dashed-ident> specified in the prelude is the rule’s name. If multiple @position-try rules are declared with the same name, the last one in document order "wins".
The @position-try rule only accepts the following properties:
It is invalid to use !important on the properties in the <declaration-list>. Doing so causes the property it is used on to become invalid, but does not invalidate the @property-try rule as a whole.
All of the properties in a @position-try are applied to the box as part of the Position Fallback Origin, a new cascade origin that lies between the Author Origin and the Animation Origin.
Similar to the Animation Origin, use of the revert value acts as if the property was part of the Author Origin, so that it instead reverts back to the User Origin. (As with the Animation Origin, however, revert-layer has no special behavior and acts as specified.)
Note: The accepted @position-try properties are the smallest group of properties that affect just the size and position of the box itself, without otherwise changing its contents or styling. This significantly simplifies the implementation of position fallback while addressing the fundamental need to move an anchor-positioned box in response to available space. Since these rules override normal declarations in the Author Origin, this also limits the poor interactions of @position-try declarations with the normal cascading and inheritance of other properties. It is expected that a future extension to container queries will allow querying an element based on the position fallback it’s using, enabling the sort of conditional styling not allowed by this restricted list.
Note: If multiple boxes using different anchors want to use the same fallback positioning, just relative to their own anchor elements, omit the <anchor-name> in anchor() and specify each box’s anchor in position-anchor instead.
Note: The most common types of fallback positioning (putting the positioned box on one side of the anchor normally, but flipping to the opposite side if needed) can be done automatically with keywords in position-try-fallbacks, without using @position-try at all.
5.5. Applying Position Fallback
When a positioned box overflows its inset-modified containing block, and has a non-empty position options list, it determines position fallback styles to attempt to find an option that avoids overflow.
These modified styles are applied to the element via interleaving, so they affect computed values (and can trigger transitions/etc) even though they depend on layout and used values.
-
With new styles inserted into the cascade in the position fallback origin, resolve the cascade, and perform enough layout to determine el’s used styles.
-
Return el’s used styles.
-
Let base styles be the current used styles of el.
-
For each option in the position options list:
-
Let adjusted styles be the result of applying a position option option to el.
-
Let el rect be the size and position of el’s margin box, when laid out with adjusted styles.
Let cb rect be the size and position of el’s inset-modified containing block.
-
If el has a snapshotted scroll offset:
-
Set el rect to the result of shifting it by the snapshotted scroll offset.
-
Recalculate el’s inset-modified containing block, with any non-auto inset values shifted by the snapshotted scroll offset.
If el has a non-none position-area value, then for the purpose of the preceding calculation, shift the value of anchor(start) and anchor(end) in the position-area grid by the snapshotted scroll offset as well.
Note: In other words, top: anchor(bottom); bottom: auto; and position-area: bottom; should result in the same IMCB for the purpose of these overflow calculations.
Set cb rect to this result.
-
If cb rect was negative-size in either axis and corrected into zero-size, continue.
-
-
If el rect is not fully contained within cb rect, continue.
-
Return adjusted styles.
-
-
Assert: The previous step finished without finding a position option that avoids overflow.
-
If el has a last successful position option, return the result of applying a position option, using that option, to el.
-
Return base styles.
Note: Descendants overflowing el don’t affect this calculation, only el’s own margin box.
During a full layout pass, once a box has determined its fallback styles (or determined it’s not using any), laying out later boxes cannot change this decision.
Layout does not "go backward", in other words.
ResizeObserver
events are determined and delivered:
-
If a box el is absolutely positioned, set its last successful position option to the set of accepted @position-try properties (and values) that it’s currently using.
-
Otherwise, if el has a last successful position option and if any of the following are true of it, remove its last successful position option:
-
el is not absolutely positioned
-
el’s computed value for position-try-fallbacks has changed
-
Any of the @position-try rules referenced by el’s position-try-fallbacks have been added, removed, or mutated.
Then, determine position fallback styles for el and set its last successful position option to the set of accepted @position-try properties (and values) that it’s now using.
-
Note: The timing of this recording/removal is intentionally identical to the treatment of last remembered sizes.
Implementations may choose to impose an implementation-defined limit on the length of position options lists, to limit the amount of excess layout work that may be required. This limit must be at least five.
#myPopover{ position : fixed; top : anchor ( --button bottom); left : anchor ( --button left); position-try-fallbacks : flip-inline, flip-block, flip-block flip-inline; /* The popover is at least as wide as the button */ min-width:anchor-size ( --button width); /* The popover is at least as tall as 2 menu items */ min-height:6 em ; }
6. DOM Interfaces
6.1. The CSSPositionTryRule interface
The CSSPositionTryRule
interface represents
the @position-try rule:
[Exposed =Window ]interface :
CSSPositionTryRule CSSRule {readonly attribute CSSOMString name ; [SameObject ,PutForwards =cssText ]readonly attribute CSSPositionTryDescriptors style ; }; [Exposed =Window ]interface :
CSSPositionTryDescriptors CSSStyleDeclaration {attribute CSSOMString ;
margin attribute CSSOMString ;
marginTop attribute CSSOMString ;
marginRight attribute CSSOMString ;
marginBottom attribute CSSOMString ;
marginLeft attribute CSSOMString ;
marginBlock attribute CSSOMString ;
marginBlockStart attribute CSSOMString ;
marginBlockEnd attribute CSSOMString ;
marginInline attribute CSSOMString ;
marginInlineStart attribute CSSOMString ;
marginInlineEnd attribute CSSOMString ;
margin-top attribute CSSOMString ;
margin-right attribute CSSOMString ;
margin-bottom attribute CSSOMString ;
margin-left attribute CSSOMString ;
margin-block attribute CSSOMString ;
margin-block-start attribute CSSOMString ;
margin-block-end attribute CSSOMString ;
margin-inline attribute CSSOMString ;
margin-inline-start attribute CSSOMString ;
margin-inline-end attribute CSSOMString ;
inset attribute CSSOMString ;
insetBlock attribute CSSOMString ;
insetBlockStart attribute CSSOMString ;
insetBlockEnd attribute CSSOMString ;
insetInline attribute CSSOMString ;
insetInlineStart attribute CSSOMString ;
insetInlineEnd attribute CSSOMString ;
top attribute CSSOMString ;
left attribute CSSOMString ;
right attribute CSSOMString ;
bottom attribute CSSOMString ;
inset-block attribute CSSOMString ;
inset-block-start attribute CSSOMString ;
inset-block-end attribute CSSOMString ;
inset-inline attribute CSSOMString ;
inset-inline-start attribute CSSOMString ;
inset-inline-end attribute CSSOMString ;
width attribute CSSOMString ;
minWidth attribute CSSOMString ;
maxWidth attribute CSSOMString ;
height attribute CSSOMString ;
minHeight attribute CSSOMString ;
maxHeight attribute CSSOMString ;
blockSize attribute CSSOMString ;
minBlockSize attribute CSSOMString ;
maxBlockSize attribute CSSOMString ;
inlineSize attribute CSSOMString ;
minInlineSize attribute CSSOMString ;
maxInlineSize attribute CSSOMString ;
min-width attribute CSSOMString ;
max-width attribute CSSOMString ;
min-height attribute CSSOMString ;
max-height attribute CSSOMString ;
block-size attribute CSSOMString ;
min-block-size attribute CSSOMString ;
max-block-size attribute CSSOMString ;
inline-size attribute CSSOMString ;
min-inline-size attribute CSSOMString ;
max-inline-size attribute CSSOMString ;
placeSelf attribute CSSOMString ;
alignSelf attribute CSSOMString ;
justifySelf attribute CSSOMString ;
place-self attribute CSSOMString ;
align-self attribute CSSOMString ;
justify-self attribute CSSOMString ;
positionAnchor attribute CSSOMString ;
position-anchor attribute CSSOMString ;
positionArea attribute CSSOMString ; };
position-area
Its name
attribute
represents the name declared in the rule’s prelude.
Its style
attribute
represents the properties declared in the rule’s body,
in the specified order.
On getting, it must return a CSSPositionTryDescriptors
object
for the @position-try at-rule,
with the following properties:
- computed flag
-
Unset
- readonly flag
-
Unset
- declarations
-
The declared descriptors in the rule, in specified order.
- parent CSS rule
-
The context object
- owner node
-
Null
7. Appendix: Style & Layout Interleaving
Style & layout interleaving is a technique where a style update can occur on a subtree during the layout process, resulting in retroactive updates to elements’ computed styles.
This is not the correct spec for this concept, it should probably go in Cascade, but I need a sketch of it to refer to.
Note: Style & layout interleaving is already used with container queries and container query lengths. A length like 10cqw is resolved into a computed length using layout information about the query container’s size, which can thus trigger transitions when the container changes size between layouts.
The accepted @position-try properties are also interleaved when resolving fallback (see position-try).
Obviously this needs way more details filled in, but for now "act like you already do for container queries" suffices. That behavior is also undefined, but at least it’s interoperable (to some extent?).
8. Security Considerations
No Security issues have been raised against this document.
9. Privacy Considerations
No Privacy issues have been raised against this document.
10. Changes
Significant changes since the 26 March 2024 Working Draft:
-
Renamed inset-area to position-area
-
Added position-anchor: auto to use the implicit anchor element as the default anchor element
-
Added top-layer details to the acceptable anchor element definition
-
Removed the implicit keyword from the anchor functions (omitting an anchor name does the same thing)
-
Removed the "reduce the size of the available space" behavior from anchor-center, now that Align defines how to shift to avoid overflow.
-
Added position-visibility, to hide the positioned element in some cases.
-
Added a11y guidance (§ 3.7 Accessibility Implications)
-
Expanded the set of properties that anchor-size() is used in (to all the relevant accepted @position-try properties).
-
Renamed position-try-options to position-try-fallbacks
-
Removed the inset-area() function from position-try-fallbacks (you just use a position-area value directly now).
-
Defined how to swap due to a try-tactic.
-
Expanded the details of how to determine position fallback styles.