1. Introduction
CSS absolute positioning allows authors to place elements anywhere on the page, without regard to the layout of other elements besides their containing block. This flexibility can be very useful, but also very limiting—often you want to position relative to some other element. Anchor positioning (via the anchor functions anchor() and anchor-size()) allows authors to achieve this, "anchoring" an absolutely-positioned element to one or more other elements on the page, while also allowing them to try several possible positions to find the "best" one that avoids overlap/overflow.
For example, an author might want to position a tooltip centered and above the targeted element, unless that would place the tooltip offscreen, in which case it should be below the targeted element. This can be done with the following CSS:
.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 ; }
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, 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 anchor elements for a given positioned element, so 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 element, the target anchor element will be the last one in DOM order. anchor-scope can be used to limit what names are visible to a given element.
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 a given positioned element.
TODO: Fill in an example using the "anchor" attribute (once that finally lands in the HTML spec), which sets the implicit anchor element.
Implicit anchor elements can be referenced with the implicit keyword, rather than referring to some anchor-name value.
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.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 implicit, or nothing (a missing specifier).
Note: The general rule captured by these conditions is that an element can only be an anchor if it’s fully laid out before the positioned element that wants to reference it is laid out. CSS’s layout rules provide some useful guarantees about this, depending on the anchor and positioned element’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 target anchor element for query el given the query el’s default anchor specifier.
-
If anchor spec is implicit:
-
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.)
-
Either el is a descendant of query el’s containing block, or query el’s containing block is the initial containing block.
-
If el has the same containing block as query el, then either el is not absolutely positioned, or el precedes query el in the tree order.
-
If el has a different containing block from query el, then the last containing block in el’s containing block chain before reaching query el’s containing block is either not absolutely positioned or precedes query el in the tree order.
-
el is either an element or a part-like pseudo-element.
-
If el is in the skipped contents of another element, then query el is in the skipped contents of that same element.
Note: In other words, query el can anchor to el if they’re both in the same skipped "leaf", but it can’t anchor "across" leafs.
-
el is in scope for query el, per the effects of anchor-scope on query el or its ancestors.
2.4. Default Anchors: the position-anchor property
Name: | position-anchor |
---|---|
Value: | auto | <anchor-element> |
Initial: | auto |
Applies to: | absolutely positioned elements |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | discrete |
The position-anchor property defines the default anchor specifier for all anchor functions on the element, allowing multiple elements to use the same set of anchor functions (and position options lists!) while changing which anchor element each is referring to. position-anchor is a reset-only sub-property of position.
The target anchor element selected by the default anchor specifier (if one exists) is the element’s default anchor element.
Its values are identical to the <anchor-element> term in anchor() and anchor-size(), except that the auto value is equivalent to omitting the <anchor-element> in anchor() (referring to the implicit anchor element, if one exists).
.anchored{ position : absolute; top : calc ( .5 em +anchor ( outside)); /* Since no anchor name was specified, this automatically refers to the default anchor element. */ } .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 element (which 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 element relying on it is also not skipped. (Unless the anchor and the positioned element are both under the same content-visibility: auto element; they can’t cyclicly keep each other visible.)
3. Anchor-Based Positioning
An absolutely-positioned element can position itself relative to one or more anchor elements on the page.
The position-area property offers a convenient grid-based concept for positioning relative to the default anchor element; for more complex positioning or positioning relative to multiple elements, the anchor() function can be used in the inset properties to explicitly refer to edges of an anchor element.
3.1. The position-area Property
Name: | position-area |
---|---|
Value: | none | <position-area> |
Initial: | none |
Applies to: | positioned elements with a default anchor element |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | TBD |
Most common use-cases of anchor positioning only need to worry about the edges of the positioned element’s containing block, and the edges of the default anchor element. These lines can be thought of as defining a 3x3 grid; position-area lets you easily set up the positioned element’s inset properties by specifying what area of this position-area grid you want the positioned element to be 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 element does not have a default anchor element, or is not an absolutely-positioned element, 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 element’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 element. Again, see § 3.1.1 Resolving <position-area>s for details.
-
Any auto inset properties resolve to 0.
-
The element is not considered to be in a legacy alignment mode in either axis.
-
This behavior makes it more likely that positioned elements remain visible and within their intended bounds, even when their containing block ends up smaller than anticipated.
For example, an position-area: bottom span-right value lets the positioned element stretch from its anchors left edge to its containing block’s right edge, and left-aligns it in that space by default. But if the positioned element 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 3x3 grid, composed of four grid lines in each axis. In order:
-
the start edge of the element’s pre-modification containing block, or the anchor-start() edge of the default anchor element if that is more start-ward
-
the anchor(start) edge of the default anchor element
-
the anchor(end) edge of the default anchor element
-
the end edge of the element’s pre-modification containing block, or the anchor-start() edge fo the default anchor element if that is more end-ward.
An <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 element’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 element’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 element’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 element 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 element 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 element can use the anchor() function as a value in its inset properties to refer to the position of one or more anchor elements. The anchor() function resolves to a <length>. It is only valid to be used in the inset properties.
<anchor()> = anchor( <anchor-element>? && <anchor-side>, <length-percentage>? ) <anchor-element> = <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-element> 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 implicit anchor element defined for the element, 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 element’s sides, depending on which inset property it’s used in. inside refers to the same side as the inset property (attaching the element to the "inside" of the anchor), while outside refers to the opposite.
- top
- right
- bottom
- left
- right
-
Refers to the specified side of the anchor element.
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 element in the same axis as the inset property it’s used in, by resolving the keyword against the writing mode of either the positioned element (for self-start and self-end) or the positioned element’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 elements' 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 element’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 element 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 element is additionally visually shifted by its snapshotted scroll offset, as if by an additional translate() transform.
.bar
element’s top edge
with the --foo anchor’s top edge.
On the other hand,
in .bar { bottom: anchor(--foo top); },
it will instead resolve to the length
that’ll line up the .bar
element’s bottom edge
with the --foo anchor’s top edge.
Since top and bottom values specify insets from different edges (the top and bottom 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 element 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 element 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 element—requires careful and somewhat complex set-up to achieve.
The new anchor-center value makes this case extremely simple: if the positioned element has a default anchor element, then it is aligned so as to center itself over the default anchor element in the relevant axis.
Additionally, any auto inset properties resolve to 0.
If the element is not absolutely positioned, or does not have a default anchor element, 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 element’s original containing block, by default it will "shift" from being purely centered, to instead 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 element and the positioned element’s containing block are at their initial scroll position. This means a positioned element 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:
-
query el has a default anchor element.
-
At least one anchor() function on query el’s used inset properties in the axis refers to a target anchor element with the same nearest scroll container ancestor as query el’s default anchor element, or query el’s used self-alignment property value in the axis is anchor-center.
Note: If query el has a position options list, then whether it needs scroll adjustment in an axis is also affected by the applied fallback style.
query el’s snapshotted scroll offset is a pair of lengths for the horizontal and vertical axises, respectively. Each length is calculated as:
-
If query el 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 element in the same axis, up to but not including query el’s containing block;
-
Otherwise, the length is 0.
If the default anchor element, or ancestors between it and query el’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 element 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 element’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 element) is used for scroll adjustment—if multiple anchors could be adjusted for, then a positioned element 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 element 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 element’s size depend on an anchor’s scroll position, the positioned element must be inside of the same scroll container, so the two elements 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 element.
-
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-element> 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 elements |
Inherited: | no |
Percentages: | n/a |
Computed value: | as specified |
Canonical order: | per grammar |
Animation type: | discrete |
There are times when an element’s anchors are not appropriate for positioning the element with, and it would be better to simply not display the element at all. position-visibility provides several conditions where this could be the case.
- always
-
This property has no effect. (The element is displayed without regard for its anchors or its overflowing status.)
- anchors-valid
-
If any of the element’s required anchor references do not resolve to a target anchor element, the element 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 element has a default anchor element but the anchor element is clipped by intervening elements, this element is also .
- no-overflow
-
If the element overflows its inset-modified containing block, even after applying position-try, then the element 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 elements 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.
Making an element strongly hidden makes it act 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 element 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 element can use the anchor-size() function in its sizing properties to refer to the size of one or more anchor elements. The anchor-size() function resolves to a <length>. It is only valid to be used in the sizing properties.
4.1. The anchor-size() Function
anchor-size() = anchor-size( [ <anchor-element> || <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 element (for self-block and self-inline) or the writing mode of the element’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 on an absolutely-positioned element.
-
There is a target anchor element for the element it’s used on, and the <anchor-element> 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 element might be anywhere on the page, so positioning an element in any particular fashion (such as above the anchor, or the right of the anchor) might result in the positioned element overflowing its containing block or being positioned partially off screen.
To ameliorate this, an absolutely positioned element can use the position-try-fallbacks property to refer to several variant sets of positioning/alignment properties (generated from the element’s existing styles, or specified in @position-try rules) that the UA can try if the element overflows its initial position. Each is applied to the element, one by one, and the first that doesn’t cause the element 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 element 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 elements |
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 element’s existing computed style.
Values have the following meanings:
- none
-
The property has no effect; the element’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 element’s computed style, by swapping due to a try-tactic according to the specified keyword, and adding the constructed position option to the element’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, then applies its position option to the element’s base style, transforms it according to the specified <try-tactic>, and then adds the result to the element’s position options list.
Otherwise, does nothing.
- <'position-area'>
-
Automatically creates a position option composed solely of an 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 substitution functions in styles.
Need to define a term to hook for substitution functions, to make sure we can hit var(), env(), random(), etc...
-
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 elements |
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 a position option using that option to the element, 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; top : anchor ( bottom); left : anchor ( left); 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{ top : 0 ; bottom : anchor ( top); 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 top: 0 (the top edge of the viewport) to bottom: anchor(top) (the top of the anchor), so they’ll retain their specified order. This causes the element 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 will be applied to an element 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 invalid the @property-try rule as a whole.
All of the properties in a @position-try are applied to the element 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 changing its contents area. This significantly simplifies the implementation of position fallback, without reducing the possible behaviors overly much. It is expected that a future extension to container queries will allow querying an element based on the position fallback it’s using, allowing many cases not allowed by this restricted list to be handled.
Note: If multiple elements using different anchors want to use the same fallback positioning, just relative to their own anchor elements, omit the <anchor-element> in anchor() and specify each element’s anchor in position-anchor instead.
Note: The most common types of fallback positioning (putting the positioned element 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 element overflows its inset-modified containing block, and has a non-empty position options list, it determines the position fallback styles for the element 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 tho 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 an element has determined its fallback styles (or determined it’s not using any), laying out later elements cannot change this decision.
Layout does not "go backward", in other words.
ResizeObserver
events are determined and delivered:
-
If an element 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 an absolutely positioned element
-
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 the position fallback styles of 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.