> value specified in the function.
If any of these conditions are false,
the ''anchor()'' function [=computed value|computes=] to its specified fallback value.
If no fallback value is specified,
it makes the declaration referencing it [=invalid at computed-value time=].
Taking Scroll Into Account {#scroll}
--------------------------
For performance reasons,
implementations usually perform scrolling on a separate scrolling/"compositing" thread,
which has very limited capabilities
(simple movement/transforms/etc., but no layout or similar expensive operations)
and thus can be relied upon to respond to scrolling
fast enough to be considered "instant" to human perception.
If scrolling just causes an anchor-positioned element to move,
there is in theory no issue;
the movement can be performed on the scrolling thread
so the positioned element moves smoothly with the scrolling content.
However, [=anchor positioning=] allows an element
to make the positions of its own opposite edges
depend on things in different scrolling contexts,
which means scrolling could move just one edge and cause a size change,
and thus perform layout.
This can't be performed on the scrolling thread!
To compensate for this,
while still allowing as much freedom to anchor to various elements as possible,
[=anchor positioning=] uses a combination of [=remembered scroll offsets=]
and [=compensating for scroll=].
The details here are technical, but the gist is:
* When a positioned element is first displayed,
or when it [[#fallback|changes fallbacks]],
its position is correctly calculated
according to the up-to-date position of all [=anchor references=].
If these [=anchor references=] are in a different scroll context,
their total scroll offsets are memorized,
and layout will continue using those memorized offsets,
even if those elements are scrolled later.
(Only the scroll offsets are memorized;
their actual laid-out positions are freshly calculated each time
and remain accurate.)
They'll only recalculate if the positioned element stops being displayed and starts again,
or changes fallbacks.
* The one exception to this is the [=default anchor element=];
if it's scrolled away from its [=remembered scroll offset=],
the positioned element moves with it.
Because this is *purely* a shift in position,
the positioned element can't change size
or otherwise require layout in response.
The end result is that [=anchor positioning=] should generally "just work",
regardless of what the element is anchoring to,
but it might be limited in how it can respond to scrolling.
An anchor recalculation point
occurs for an [=absolutely positioned element=]
whenever that element begins generating boxes
(aka switches from ''display:none'' or ''display:contents''
to any other 'display' value),
identical to when it starts running CSS animations.
An [=anchor recalculation point=] also occurs for an element
when [=determining position fallback styles=] for that element;
if it changes fallback styles as a result,
it uses the result of the [=anchor recalculation point=]
associated with the chosen set of fallback styles.
When an [=anchor recalculation point=] occurs for an element |abspos|,
then for every element |anchor|
referenced by one of |abspos|'s [=anchor references=],
it associates a remembered scroll offset
equal to the current sum of the [=scroll offsets=]
of all [=scroll container=] ancestors
of |anchor|,
up to but not including
|abspos|'s [=containing block=].
The [=remembered scroll offset=] also accounts
for other scroll-dependent positioning changes,
such as ''position: sticky''.
If |abspos| has a [=default anchor element=],
it always calculates a [=remembered scroll offset=] for it,
even if |abspos| doesn't actually have an [=anchor reference=] to it.
All [=anchor references=] are calculated
as if all [=scroll containers=] were at their [=initial scroll position=],
and then have their associated [=remembered scroll offset=]
added to them.
Issue: Transforms have the same issue as scrolling,
so Anchor Positioning similarly doesn't pay attention to them normally.
Can we go ahead and incorporate the effects of transforms here?
The above allows a positioned element to respond
to the scroll positions of its [=anchor references=] once,
but if any of them are scrolled,
the positioned element will no longer appear to be anchored to them
(tho it will continue to respond to their non-scrolling movement).
While this problem can't be solved in general,
we can respond to the scrolling of one [=anchor reference=];
specifically, the [=default anchor element=]:
An [=absolutely positioned box=] |abspos|
compensates for scroll
in the horizontal or vertical axis
if both of the following conditions are true:
* |abspos| has a [=default anchor box=].
* |abspos| has an [=anchor reference=] to its [=default anchor box=]
or at least to something in the same scrolling context,
aka at least one of:
* |abspos|'s used [=self-alignment property=] value
in that axis is ''anchor-center'';
* |abspos| has a non-''position-area/none'' value for 'position-area'
* at least one ''anchor()'' function on |abspos|'s
[=used value|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 [=compensates for scroll=] in an axis
is also affected by the applied fallback style.
|abspos|'s default scroll shift is a pair of lengths
for the horizontal and vertical axises, respectively.
Each length is calculated as:
* If |abspos| is [=compensating for scroll=] in the axis,
then the length is the difference between the [=remembered scroll offset=]
of the [=default anchor element=]
and what its current [=remembered scroll offset=] would be
if it were recalculated.
* Otherwise, the length is 0.
After layout has been performed for |abspos|,
it is additionally shifted by the [=default scroll shift=],
as if affected by a [=transform=]
(before any other transforms).
Issue: Define the precise timing of the snapshot:
updated each frame,
before style recalc.
Issue: Similar to [=remembered scroll offset=],
can we pay attention to transforms on the [=default anchor element=]?
Note: While [=remembered scroll offsets=]
affect the value of ''anchor()'' functions,
[=default scroll shift=] directly shifts the element,
after determining the value of its [=inset properties=],
applying alignment,
etc.
This is usually indistinguishable,
but cases like ''round(anchor(outside), 50px)'',
which transform the [=default anchor element's=] position
in a non-linear fashion,
will expose the difference in behavior.
Conditional Hiding: the 'position-visibility' property {#position-visibility}
------------------------------------------------------
Name: position-visibility
Value: always | [ anchors-valid || anchors-visible || no-overflow ]
Initial: anchors-visible
Applies to: [=absolutely positioned boxes=]
Percentages: n/a
Inherited: no
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's 'visibility' property computes to ''force-hidden''.
Issue: What is a required anchor reference?
''anchor()'' functions that don't have a fallback value;
the default anchor *sometimes*?
Need more detail here.
Issue: 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 [=invisible=] or [=clipped by intervening boxes=],
the box's 'visibility' property computes to ''force-hidden''.
: no-overflow
::
If the box overflows its [=inset-modified containing block=]
even after applying 'position-try',
the box's 'visibility' property computes to ''force-hidden''.
An anchor box |anchor|
is clipped by intervening boxes
relative to a positioned box |abspos| relying on it
if |anchor|'s [=ink overflow rectangle=]
is fully clipped by a box
which is an ancestor of |anchor|
but a descendant of |abspos|'s containing block.
Clipping in this case refers only to clipping due to 'overflow',
or other effects (such as [=paint containment=])
that clip to the [=overflow clip edge=].
Whether or not |anchor| is [=clipped by intervening boxes=]
is determined immediately prior to triggering {{ResizeObserver}}s.
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.
Issue: Make sure this definition of clipped
is consistent with View Transitions,
which wants a similar concept.
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.
Anchor-Based Alignment {#alignment}
======================
Area-specific Default Alignment
When using 'position-area',
the behavior of the ''align-self/normal'' [=self-alignment=] value
changes depending on the <> value,
to align the box towards the anchor:
* If the only the center track in an axis is selected,
the default alignment in that axis is ''align-self/center''.
* If all three tracks are selected,
the default alignment in that axis is ''align-self/anchor-center''.
* Otherwise, the default alignment in that axis
is toward the non-specified side track:
if it's specifying the “start” track of its axis,
the default alignment in that axis is ''align-self/end''; etc.
For example, assuming an English-equivalent writing mode (horizontal-tb, ltr),
then the value ''span-x-start top'' resolves to
the ''position-area/start'' region of the vertical axis,
and the ''position-area/start'' and ''position-area/center'' regions of the horizontal axis,
so the default alignments will be ''align-self: end''
(making the box's bottom [=margin edge=] flush with the bottom of the ''position-area/top'' region)
and ''justify-self: end''
(making the box's end-side [=margin edge=] flush with the end side of the ''position-area/span-start'' region).
If the box overflows its [=inset-modified containing block=],
but would still fit within its [=original containing block=],
by default it will “shift” to stay within its [=original containing block=],
even if that violates its normal alignment.
See [[css-align-3#overflow-values]] for details.
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.
Centering on the Anchor: the ''anchor-center'' alignment value {#anchor-center}
--------------------------------------------------------------
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 ''top/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: When using ''anchor-center'', by default
if the anchor is too close to the edge of the box's
[=original containing block=],
it will “shift” from being purely centered,
in order to remain within the [=original containing block=].
See [[css-align-3#overflow-values]] for more details.
Conditional Centering: the ''dialog'' alignment value {#conditional-centering}
-----------------------------------------------------
Name: justify-self, align-self, justify-items, align-items
New Values: dialog
Dialog boxes are often displayed centered on the screen,
unless they are anchored to some other element.
The new dialog value
allows this behavior to be specified:
if the box is [=absolutely positioned=]
and its 'position-area' value is not ''position-area/none'',
it is aligned as per ''align-self/normal''
(see [[#position-area-alignment]]);
otherwise,
it is aligned as per ''align-self/center''.
Anchor-Based Sizing {#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 <>.
It is only allowed in the [=accepted @position-try properties=]
(and is otherwise invalid).
The ''anchor-size()'' Function {#anchor-size-fn}
------------------------------
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( [ <> || <> ]? , <>? )
<> = width | height | block | inline | self-block | self-inline
The ''anchor-size()'' function is similar to ''anchor()'',
and takes the same arguments,
save that the <> keywords are replaced with <>,
referring to the distance between two opposing sides.
The physical <> 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 <> 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 ''anchor-size()/block'' and ''anchor-size()/inline'').
If the <> 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 [=resolvable anchor-size function=]
resolves at [=computed value=] time
(via [=style & layout interleaving=])
to the <> separating the relevant border edges
(either left and right, or top and bottom,
whichever is in the specified axis)
of the [=target anchor element=].
Resolution of ''anchor-size()''
An ''anchor-size()'' function is a
resolvable anchor-size function
only if all the following conditions are true:
* It's applied to an [=absolutely positioned box=].
* There is a [=target anchor element=]
for the box it's used on,
and the <> 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=].
Overflow Management {#fallback}
===========================
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.
Giving Fallback Options: the 'position-try-fallbacks' property {#position-try-fallbacks}
--------------------------------
Name: position-try-fallbacks
Value: none | [ [<> || <>] | <<'position-area'>> ]#
Initial: none
Inherited: no
Applies to: [=absolutely positioned boxes=]
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 <> 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.
: <>
::
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.
: <>
::
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=].
<> = 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 [=block-start|start=]-[=inline-start|start=] corner
to the [=block-end|end=]-[=inline-end|end=] corner.
If multiple keywords are given,
the transformations are composed in order
to produce a single [=position option=].
: <> || <>
::
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 <>
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.
To
swap due to a try-tactic
the styles of a box's element |el|
between two directions |directions|,
returning a set of styles:
0. If |directions| are opposites along the same axis,
they are “opposing”.
Otherwise (when they are specifying different axises),
they are “perpendicular”.
1. Determine the specified values of the [=accepted @position-try properties=]
on |el|,
and let |styles| be the result.
2. [=substitute a var()|Substitute variables=],
''env()'' functions,
and similar [=arbitrary substitution functions=]
in |styles|.
For ''env()'' functions,
if the referenced [=CSS/environment variable=] is associated with a direction or axis
(such as ''env()/safe-area-inset-top''),
switch the referenced [=CSS/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.)
3. 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.
4. 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 <
> 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 <>
(or ''align-self/left''/''align-self/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 <>
rather than a <>.
* 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''.
5. Return |styles|.
Determining Fallback Order: the 'position-try-order' property {#position-try-order-property}
----------------------------------
Name: position-try-order
Value: normal | <>
Initial: normal
Applies to: [=absolutely positioned boxes=]
Inherited: no
Animation Type: discrete
This property specifies the order in which
the [=position options list=] will be tried.
<> = 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
::
For each entry in the [=position options list=],
[=apply a position option|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.
For example, the following styles
will initially position the popup list below its anchoring button,
but if that overflows,
will decide whether to keep the popup list below the anchor
or move it above,
depending on which option gives it the most space.
.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.
The 'position-try' Shorthand {#position-try-prop}
-------------------------------------------------
Name: position-try
Value: <<'position-try-order'>>? <<'position-try-fallbacks'>>
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.
The ''@position-try'' Rule {#fallback-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 <> {
<>
}
The <> 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=]:
* [=inset properties=]
* [=margin properties=]
* [=sizing properties=]
* [=self-alignment properties=]
* 'position-anchor'
* 'position-area'
It is invalid to use ''!important'' on the properties in the <>.
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 <> 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.
Applying Position Fallback {#fallback-apply}
--------------------------
When a positioned box
(shifted by its [=default scroll shift=])
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=].
To apply a position option to a box's element |el|,
given a [=position option=] |new styles|:
1. With |new styles| inserted into the cascade
in the [=position fallback origin=],
resolve the cascade,
and perform enough layout to determine |el|'s [=used value|used styles=].
For the purpose of calculating these styles,
a hypothetical [=anchor recalculation point=] is calculated,
and the resulting hypothetical [=remembered scroll offsets=] are used to determine |el|'s styles.
2. Return |el|'s [=used value|used styles=].
To determine position fallback styles for an element |abspos|:
1. Let |current styles| be the current used styles of |abspos|
(which might be the result of earlier fallback).
2. [=list/For each=] |option| in the [=position options list=]:
1. If |option| is currently |abspos|'s [=last successful position option=],
[=iteration/continue=].
2. Let |adjusted styles| be the result of [=applying a position option=] |option| to |abspos|.
3. Let |el rect| be the size and position of |abspos|'s margin box,
and |cb rect| be the size and position of |abspos|'s [=inset-modified containing block=],
when laid out with |adjusted styles|.
4. If |cb rect| was negative-size in either axis
and corrected into zero-size,
[=iteration/continue=].
Note: This prevents a zero-size |el rect|
from still being considered "inside"
a negative-size |cb rect|
and getting selected as a successful option.
5. If |el rect| is not fully contained within |cb rect|,
[=iteration/continue=].
6. Return |adjusted styles|,
along with the associated set of [=remembered scroll offsets=]
that were hypothetically calculated for them.
3. Assert: The previous step finished without finding a [=position option=]
that avoids overflow.
4. Return |current styles|.
Note: Descendants overflowing |el|
don't affect this calculation,
only |el|'s own [=margin box=].
Note: Because we purposely skip the [=position option=] currently in effect,
it doesn't get its [=remembered scroll offsets=] updated;
if none of the other fallbacks work
and we stick with the current styles,
all the [=remembered scroll offsets=] stay the same.
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.
For example, say you have two positioned boxes,
A and B,
with A laid out before B.
If B overflows and causes A's containing block to gain scrollbars,
this does not cause A
to go back and re-determine its fallback styles
in an attempt to avoid overflowing.
(At best, this can result in exponential layout costs;
at worst, it's cyclic and will never settle.)
Layout does not "go backward", in other words.
At the time that {{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=]
remove its [=last successful position option=]
if any of the following are true:
* Its computed 'position' value has changed,
its [=containing block=] has changed,
or it no longer generates a box.
* Its computed value for any [=longhand property|longhand=] of 'position-try' has changed.
* Its computed value for any [=@position-try property=] has changed.
* Any of the ''@position-try'' rules referenced by it
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.
For example,
the following CSS will first attempt to position a "popover"
below the element,
but if it doesn't fit on-screen will switch to being above;
it defaults to left-aligning,
but will switch to right-aligning if that doesn't fit.
#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: 6em;
}
Accessibility Implications {#accessibility}
==========
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 <{html-global/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.
DOM Interfaces {#interfaces}
==========
The CSSPositionTryRule interface {#om-position-try}
-------------------------------------
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:
: [=CSSStyleDeclaration/computed flag=]
:: Unset
: [=CSSStyleDeclaration/readonly flag=]
:: Unset
: [=CSSStyleDeclaration/declarations=]
:: The declared descriptors in the rule, in [=specified order=].
: [=CSSStyleDeclaration/parent CSS rule=]
:: The context object
: [=CSSStyleDeclaration/owner node=]
:: Null
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 value|computed styles=].
Issue: 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').
Issue: 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?).
Security Considerations {#sec}
=======================
No Security issues have been raised against this document.
Privacy Considerations {#priv}
======================
No Privacy issues have been raised against this document.
Changes {#changes}
=======
Significant changes since the 4 March October 2024 Working Draft:
* Switched the center track for 'position-area' to default to ''align-self/center'' alignment.
(Issue 11803)
* Defined the [=implicit anchor element=] of [=pseudo-elements=] to be their [=originating element=].
(Issue 11792)
* Rewrote how scrolling is handled and responded to ([[#scroll]]),
so that the positioned element can respond to its anchors being scrolled when possible
(when the positioned element is first rendered, or changes fallbacks).
(Issue 10999)
* Rebased 'position-visibility' to use the newly defined ''visibility: force-hidden'' value
instead of creating a custom concept,
in order to better enable transitions.
(Issue 10182)
* Fixed some errors in the anchor resolution algorithm.
(Issue 11030,
Issue 11170)
* Fixed some errors in the list of <> keywords.
(Issue 11372,
Issue 11371)
* Defined that 'anchor-scope' idents are [=tree-scoped names=] (and thus compare specially with [=anchor names=]).
(Issue 10525,
Issue 10526)
* Clarified that the [=computed value=] of 'position-area' does not distinguish long and short forms of logical keywords.
(Issue 11828)
* Clarified that certain ''anchor()'' resolaveability checks are at [=computed value=] time, not parse time.
(Issue 10955)
* Editorial reorganization of sections, particularly [[#position-area]].
See also Previous Changes.