CSS Anchor Positioning

Editor’s Draft,

More details about this document
This version:
https://drafts.csswg.org/css-anchor-position-1/
Latest published version:
https://www.w3.org/TR/css-anchor-position-1/
Feedback:
CSSWG Issues Repository
Inline In Spec
Editors:
Tab Atkins-Bittner (Google)
Ian Kilpatrick (Google)
Former Editor:
Jhey Tompkins (Google)
Suggest an Edit for this Spec:
GitHub Editor

Abstract

This specification defines 'anchor positioning', where a positioned element can size and position itself relative to one or more "anchor elements" elsewhere on the page.

CSS is a language for describing the rendering of structured documents (such as HTML and XML) on screen, on paper, etc.

Status of this document

This is a public copy of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

Please send feedback by filing issues in GitHub (preferred), including the spec code “css-anchor-position” in the title, like this: “[css-anchor-position] …summary of comment…”. All issues and comments are archived. Alternately, feedback can be sent to the (archived) public mailing list www-style@w3.org.

This document is governed by the 03 November 2023 W3C Process Document.

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. */
  anchor-default: --tooltip;

  /* Align the tooltip’s bottom to the top of the anchor,
     but automatically swap if this overflows the window
     to the tooltip’s top aligns to the anchor’s bottom
     instead. */
  bottom: anchor(outside);

  /* Set up a 300px-wide area, centered on the anchor.
     If centering would put part of it off-screen,
     instead clamp it to remain on-screen. */
  left: clamp(0px, anchor(center) - 150px, 100% - 300px);
  right: clamp(0px, anchor(center) - 150px, 100% - 300px);
  max-width: 300px;

  /* Center the tooltip in that area. */
  justify-self: center;
}

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. Anchor names are style-contained, scoped to a sub-tree. If there are still multiple valid anchor elements with the given anchor name, the last one is chosen.

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 new popover-related details. This makes the declared element the implicit anchor element for the element with the attribute.

Implicit anchor elements can be referenced with the implicit keyword, rather than referring to some anchor-name value.

Pseudo-element have the same implicit anchor element as its 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. 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).

To determine the target anchor element given a querying element query el and an optional anchor specifier anchor spec:
  1. If anchor spec was not passed, return the target anchor element for query el given the query el’s default anchor specifier.

  2. If anchor spec is implicit:

    1. If the Popover API defines an implicit anchor element for query el which is an acceptable anchor element for query el, return that element.

    2. 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.

  3. Otherwise, anchor spec is a <dashed-ident>. Return the last element el in tree order that satisfies the following conditions:

    If no element satisfies these conditions, return nothing.

Note: The general rule captured by these conditions is that el must be fully laid out before query el is laid out. CSS’s rules about the layout order of stacking contexts give us assurances about this, and the list of conditions above exactly rephrases the stacking context rules into just what’s relevant for this purpose, ensuring there is no possibly circularity in anchor positioning.

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.)

An element el is a acceptable anchor element for an absolutely positioned element query el if all of the following are true:

2.3. Default Anchors: the anchor-default property

Name: anchor-default
Value: <anchor-element>
Initial: implicit
Applies to: absolutely positioned elements
Inherited: no
Percentages: n/a
Computed value: as specified
Canonical order: per grammar
Animation type: discrete

The anchor-default 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.

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().

For example, in the following code both .foo and .bar elements can use the same positioning properties, just changing the anchor element they’re referring to:
.anchored {
  position: absolute;
  top: calc(.5em + anchor(outside));
  /* Since no anchor name was specified,
     this automatically refers to the
     default anchor element. */
}

.foo.anchored {
  anchor-default: --foo;
}
.bar.anchored {
  anchor-default: --bar;
}

3. Anchor-Based Positioning

An absolutely-positioned element can position itself relative to one or more anchor elements on the page.

The inset-area function 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 inset-area Property

Name: inset-area
Value: none | <inset-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; inset-area lets you easily set up the positioned element’s inset properties by specifying what area of this inset-area grid you want the positioned element to be in. Its syntax is:

<inset-area> = [
  [ left | center | right | center-left | center-right
  | x-start | x-end | center-x-start | center-x-end
  | x-self-start | x-self-end | center-x-self-start | center-x-self-end
  | all ]
  ||
  [ top | center | bottom | center-top | center-bottom
  | y-start | y-end | center-y-start | center-y-end
  | y-self-start | y-self-end | center-y-self-start | center-y-self-end
  | all ]
|
  [ block-start | center | block-end | center-block-start | center-block-end
  | self-block-start | self-block-end | center-self-block-start | center-self-block-end
  | all ]
  ||
  [ inline-start | center | inline-end | center-inline-start | center-inline-end
  | self-inline-start | self-inline-end | center-self-inline-start | center-self-inline-end
  | all ]
|
  [ start | center | end | center-start | center-end | all ]{1,2}
|
  [ self-start | center | self-end | center-self-start | center-self-end | all ]{1,2}
]
none

The property has no effect.

<inset-area>

If the element does not have a default anchor element, or is not an absolutely-positioned element, this value has no effect.

Otherwise, the property selects a region of the inset-area grid, and makes that the element’s containing block.

Note: This means that the inset properties specify offsets from the inset-area, and some property values, like max-height: 100%, will be relative to the inset-area as well.

Additionally, 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.

See § 3.1.1 Resolving <inset-area>s for details on both of these effects. If the two <inset-area-span>s do not define a valid region, this property is invalid.

Also, any auto inset properties resolve to 0.

3.1.1. Resolving <inset-area>s

The inset-area grid is a 3x3 grid, composed of four grid lines in each axis. In order:

An <inset-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

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.

center-start, center-end
center-top, center-bottom
center-y-start, center-y-end
center-x-start, center-x-end
center-block-start, center-block-end
center-inline-start, center-inline-end

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, center-top spans the first two rows—​the center row and the top row.)

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, 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, all start is equivalent to all inline-start.)

If only a single keyword is given, it behaves as if the second keyword is 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 all, but center is equivalent to center center.)


The <inset-area> also implies a default self-alignment, which will be used if the self-alignment property on the element is normal:

For example, assuming an English-equivalent writing mode (horizontal-tb, ltr), then the value center-x-start top resolves to the "start" region of the vertical axis, and the "start" and "center" regions of the horizontal axis, so the default alignments will be align-self: end; and justify-self: anchor-center;
An example of inset-area: center-x-start top positioning.

Note: When the default anchor element is partially or completely outside of the pre-modified containing block, some of the inset-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>.

<anchor()> = anchor( <anchor-element>? <anchor-side>, <length-percentage>? )
<anchor-element> = <dashed-ident> | implicit
<anchor-side> = inside | outside
           | top | left | right | bottom
           | start | end | self-start | self-end
           | <percentage> | center

The anchor() function has three arguments:

Computed value for anchor() probably needs to be the anchor() function, but with the target anchor element resolved. This allows for transitions to work properly with tree-scoped names, and with changing anchor elements. See Issue 8180.

An anchor() function representing a valid anchor function resolves at used value time 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).

If the target anchor element is fragmented, the axis-aligned bounding rectangle of the fragments' border boxes is used instead.

Do we need to control which box we’re referring to, so you can align to padding or content edge?

The positioned element is additionally visually shifted by its snapshotted scroll offset, as if by an additional translate() transform.

For example, in .bar { top: anchor(--foo top); }, the anchor() will resolve to the length that’ll line up the .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.

Because the anchor() function resolves to a <length>, it can be used in math functions like any other length.

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(--x 50%);
  --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. However, the available space for the positioned element in the relevant axis is reduced to the size of the largest rectangle that is centered on the default anchor element and doesn’t overflow the inset-modified containing block. (Possibly being zero-sized, if the anchor’s center is not within the inset-modified containing block.)

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.

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:

An absolutely-positioned element query el needs scroll adjustment in the horizontal or vertical axis if both of the following conditions are true:

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:

Define the precise timing of the snapshot: updated each frame, before style recalc.

If query el has an additional fallback-bounds rect, similarly calculate the sum of scroll offsets of all scroll container ancestors of the element generating the additional fallback-bounds rect, up to but not including query el’s containing block, and subtract that summed offset from the additional fallback-bounds rect’s position.

3.5. Validity

An anchor() function is a valid anchor function only if all the following conditions are true:

If any of these conditions are false, the anchor() function resolves to its specified fallback value. If no fallback value is specified, it resolves to 0px.

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>.

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).

An anchor-size() function representing a valid anchor-size function resolves 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:

If any of these conditions are false, the anchor-size() function resolves to its specified fallback value. If no fallback value is specified, it resolves to 0px.

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-fallback property to refer to a @position-fallback block, giving a list of possible style rules to try out. 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.

5.1. Giving Fallback Options: the position-try-options property

Name: position-try-options
Value: none | [ [<dashed-ident> || <try-tactic>] | inset-area( <'inset-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 the margin, sizing, inset, and self-alignment property values among the element’s sides, 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.

Define how the values themselves are changed upon flipping: anchor(top) becomes anchor(bottom); start becomes end; etc.

<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.

inset-area( <'inset-area'> )

Automatically creates a position option composed solely of an inset-area property with the given value.

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-options.

most-width
most-height
most-block-size
most-inline-size

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.

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;
  anchor-default: --foo;
  top: anchor(bottom);
  left: anchor(left);
  align-self: start;
  position-try-options: --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-options'>
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-options 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-options,

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".

Or should they cascade together?

The @position-try rule only accepts the following properties:

What exactly are the constraints that determine what’s allowed here? Current list is based off of what’s reasonable from Chrome’s experimental impl. We can make a CQ that keys off of which fallback was used to allow more general styling, at least for descendants.

Would be useful to be able to detect when your anchor(s) are fully off-screen and suppress your display entirely. For example, tooltips living outside the scroller holding the text they’re anchored to don’t want to just hover over arbitrary parts of the page because their anchor happens to have that position relative to the scrollport.

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 anchor-default 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-options, 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 iterates the position options list, applying each position option’s styles over the element’s base styles, and attempts 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. They are applied as part of the author origin, as part of a theoretical UA-controlled final cascade layer.

To apply a position option to an element el, given a position option new styles:
  1. With new styles inserted into the cascade at the very end of the author origin, resolve the cascade, and perform enough layout to determine el’s used styles.

  2. Return el’s used styles.

To determine the position fallback styles of an element el:
  1. Let base styles be the current used styles of el.

  2. For each option in the position options list:

    1. Let adjusted styles be the result of applying a position option option to el.

    2. 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.

    3. If el has a snapshotted scroll offset, subtract it from the position of both el rect and cb rect.

    4. If el rect is not fully contained within cb rect, continue.

    5. If el has an additional fallback-bounds rect, and el rect is not fully contained within it, continue.

    6. Set option as el’s last successful position option. Return adjusted styles.

  3. Assert: The previous step finished without finding a position option that avoids overflow.

  4. If el has a last successful position option, return the result of applying a position option, using that option, to el.

  5. Return base styles.

Note: Descendants overflowing el don’t affect this calculation, only el’s own margin box.

Make sure the snapshotted scroll offset stuff is correct now, given interleaving.

Add conditions for forgetting the last successful position option, similar to the last remembered size

The styles returned by determining the position fallback styles are taken as the final values for the specified properties.

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.

Nested anchors (an anchored element inside of another anchored element) present the potential for exponential blow-up of layouts when doing fallback, since the grandchild anchored element can cause scrollbars on an ancestor, changing the IMCB for the child anchored element, thus possibly causing the fallback choice to change for it.

There are strategies to avoid this, but they’re not without costs of their own. We should probably impose a maximum limit as well, to avoid this.

However, since *most* usages won’t be problematic in the first place, we don’t want to restrict them unduly just to prevent weird situations from exploding. Perhaps a complexity budget based on the branching factor at each level? Like, accumulate the product of the fallback list lengths from ancestors, and your fallback list gets limited to not exceed a total product of, say, 1k. Get too deep and you’re stuck with your first choice only! But this would allow large, complex fallback lists for top-level stuff, and even some reasonable nesting. (Length-five lists could be nested to depth of 4, for example, if we did go with 1k.)

More thought is needed.

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;
  position-fallback: --button-popover;
  overflow: auto;

  /* 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;
}

@position-fallback --button-popover {
  /* First try to align the top, left edge of the popover
  with the bottom, left edge of the button. */
  @try {
    top: anchor(--button bottom);
    left: anchor(--button left);
  }

  /* Next try to align the bottom, left edge of the popover
  with the top, left edge of the button. */
  @try {
    bottom: anchor(--button top);
    left: anchor(--button left);
  }

  /* Next try to align the top, right edge of the popover
  with the bottom, right edge of the button. */
  @try {
    top: anchor(--button bottom);
    right: anchor(--button right);
  }

  /* Finally, try to align the bottom, right edge of the popover
  with the top, right edge of the button. Other positions are possible,
  but this is the final option the author would like the rendering
  engine to try. */
  @try {
    bottom: anchor(--button top);
    right: anchor(--button right);
  }
}

6. Animating Position

Name: position-animation
Value: normal | magic
Initial: normal
Applies to: absolutely positioned elements
Inherited: no
Percentages: n/a
Computed value: normal, or an overriding position rectangle (see prose)
Canonical order: per grammar
Animation type: (see prose)

An absolutely positioned box's position and size are the result of multiple properties interacting, and this interaction is non-linear, so smoothly animating from one position to another can’t be accomplished by animating the individual properties independently.

The position-animation property resolves this conundrum, by representing the final result of applying these properties as a overriding position rectangle: a width, height, x-axis offset, and y-axis offset. The width and height represent the size of its margin box, and the x and y offsets represent its position relative to its containing block, after positioning/alignment/etc. have been performed.

If the element is not absolutely positioned, this property has no effect.

Values are:

normal

This value has no effect.

magic (name to be bikeshedded)

At computed value time, resolves to a overriding position rectangle, using interleaving.

At used value time, overrides the position and size of the element’s margin box, setting it to match the computed overriding position rectangle.

The magic value always serializes as magic; the overriding position rectangle is not observable in any way.

Interpolating to or from normal is done as a discrete step, where values of p between 0 and 1 map to normal, and other values of p map to the closer endpoint. (Similar to visibility.)

Two overriding position rectangles are interpolated by interpolating the width, height, x offset, and y offset independently as computed lengths.

Should the x/y offset be relative to the top/left corner or (CB) center to (AbsPos) center or something else?

Note: The overriding position rectangle is not directly exposed, because it functionally allows overriding the entire cascade; the overriding position rectangle causes the element to ignore all the other positioning properties, regardless of where they come from. The rectangle can nevertheless be read by authors using existing JS APIs (such as getBoundingClientRect()).

Given a change in positioning properties like:
p.start {
  top: 0px;
  bottom: 100px;
  align-self: start;
}
p.end {
  top: 100px;
  bottom: 50px;
  align-self: end;
}

The following causes a smooth animation between the two endpoints:

p {
    transition: position-animation 2s; /* magic transition yay */
    position-animation: magic;
}

This doesn’t smoothly animate (you get the "constantly recomputed, triggering fresh transitions every frame" effect that occurs if you transition color and border-color, using border-color:currentcolor):

p {
      transition: position-animation 2s, inset 2s; /* sadface */
}
Need a way to force animations to work correctly as well, like:
p {
    position-animation: magic;
    animation: foo 2s;
}

@keyframes foo {
  to {
    top: 0px;
    bottom: 100px;
    align-self: start;
  }
  from {
    top: 100px;
    bottom: 50px;
    align-self: end;
  }
}

Without some sort of magic intercept of animations (forcing them to turn into a position-animation animation) this will not smoothly animate.

7. DOM Interfaces

7.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 CSSStyleDeclaration style;
};

Its name attribute represents the name declared in the rule’s prelude.

Its style attribute represents the styles declared in the rule’s body, in the specified order. Only the accepted @position-try properties are valid in this rule.

match the concept of validity to whatever we do in similar situations.

8. 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.

An absolutely positioned box's overriding position rectangle (see position-animation) is determined by style & layout interleaving. 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?).

9. Security Considerations

No Security issues have been raised against this document.

10. Privacy Considerations

No Privacy issues have been raised against this document.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Advisements are normative sections styled to evoke special attention and are set apart from other normative text with <strong class="advisement">, like this: UAs MUST provide an accessible alternative.

Tests

Tests relating to the content of this specification may be documented in “Tests” blocks like this one. Any such block is non-normative.


Conformance classes

Conformance to this specification is defined for three conformance classes:

style sheet
A CSS style sheet.
renderer
A UA that interprets the semantics of a style sheet and renders documents that use them.
authoring tool
A UA that writes a style sheet.

A style sheet is conformant to this specification if all of its statements that use syntax defined in this module are valid according to the generic CSS grammar and the individual grammars of each feature defined in this module.

A renderer is conformant to this specification if, in addition to interpreting the style sheet as defined by the appropriate specifications, it supports all the features defined by this specification by parsing them correctly and rendering the document accordingly. However, the inability of a UA to correctly render a document due to limitations of the device does not make the UA non-conformant. (For example, a UA is not required to render color on a monochrome monitor.)

An authoring tool is conformant to this specification if it writes style sheets that are syntactically correct according to the generic CSS grammar and the individual grammars of each feature in this module, and meet all other conformance requirements of style sheets as described in this module.

Partial implementations

So that authors can exploit the forward-compatible parsing rules to assign fallback values, CSS renderers must treat as invalid (and ignore as appropriate) any at-rules, properties, property values, keywords, and other syntactic constructs for which they have no usable level of support. In particular, user agents must not selectively ignore unsupported component values and honor supported values in a single multi-value property declaration: if any value is considered invalid (as unsupported values must be), CSS requires that the entire declaration be ignored.

Implementations of Unstable and Proprietary Features

To avoid clashes with future stable CSS features, the CSSWG recommends following best practices for the implementation of unstable features and proprietary extensions to CSS.

Non-experimental implementations

Once a specification reaches the Candidate Recommendation stage, non-experimental implementations are possible, and implementors should release an unprefixed implementation of any CR-level feature they can demonstrate to be correctly implemented according to spec.

To establish and maintain the interoperability of CSS across implementations, the CSS Working Group requests that non-experimental CSS renderers submit an implementation report (and, if necessary, the testcases used for that implementation report) to the W3C before releasing an unprefixed implementation of any CSS features. Testcases submitted to W3C are subject to review and correction by the CSS Working Group.

Further information on submitting testcases and implementation reports can be found from on the CSS Working Group’s website at http://www.w3.org/Style/CSS/Test/. Questions should be directed to the public-css-testsuite@w3.org mailing list.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-ALIGN-3]
Elika Etemad; Tab Atkins Jr.. CSS Box Alignment Module Level 3. URL: https://drafts.csswg.org/css-align/
[CSS-ANCHOR-POSITION-1]
Tab Atkins Jr.; Ian Kilpatrick. CSS Anchor Positioning. URL: https://drafts.csswg.org/css-anchor-position-1/
[CSS-BOX-4]
Elika Etemad. CSS Box Model Module Level 4. URL: https://drafts.csswg.org/css-box-4/
[CSS-BREAK-4]
Rossen Atanassov; Elika Etemad. CSS Fragmentation Module Level 4. URL: https://drafts.csswg.org/css-break-4/
[CSS-CASCADE-5]
Elika Etemad; Miriam Suzanne; Tab Atkins Jr.. CSS Cascading and Inheritance Level 5. URL: https://drafts.csswg.org/css-cascade-5/
[CSS-CONTAIN-2]
Tab Atkins Jr.; Florian Rivoal; Vladimir Levin. CSS Containment Module Level 2. URL: https://drafts.csswg.org/css-contain-2/
[CSS-DISPLAY-3]
Elika Etemad; Tab Atkins Jr.. CSS Display Module Level 3. URL: https://drafts.csswg.org/css-display/
[CSS-DISPLAY-4]
CSS Display Module Level 4. Editor's Draft. URL: https://drafts.csswg.org/css-display-4/
[CSS-LOGICAL-1]
Rossen Atanassov; Elika Etemad. CSS Logical Properties and Values Level 1. URL: https://drafts.csswg.org/css-logical-1/
[CSS-OVERFLOW-3]
Elika Etemad; Florian Rivoal. CSS Overflow Module Level 3. URL: https://drafts.csswg.org/css-overflow-3/
[CSS-POSITION-3]
Elika Etemad; Tab Atkins Jr.. CSS Positioned Layout Module Level 3. URL: https://drafts.csswg.org/css-position-3/
[CSS-SCOPING-1]
Tab Atkins Jr.; Elika Etemad. CSS Scoping Module Level 1. URL: https://drafts.csswg.org/css-scoping/
[CSS-SIZING-3]
Tab Atkins Jr.; Elika Etemad. CSS Box Sizing Module Level 3. URL: https://drafts.csswg.org/css-sizing-3/
[CSS-SIZING-4]
Tab Atkins Jr.; Elika Etemad; Jen Simmons. CSS Box Sizing Module Level 4. URL: https://drafts.csswg.org/css-sizing-4/
[CSS-SYNTAX-3]
Tab Atkins Jr.; Simon Sapin. CSS Syntax Module Level 3. URL: https://drafts.csswg.org/css-syntax/
[CSS-TRANSFORMS-1]
Simon Fraser; et al. CSS Transforms Module Level 1. URL: https://drafts.csswg.org/css-transforms/
[CSS-VALUES-4]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 4. URL: https://drafts.csswg.org/css-values-4/
[CSS-WRITING-MODES-4]
Elika Etemad; Koji Ishii. CSS Writing Modes Level 4. URL: https://drafts.csswg.org/css-writing-modes-4/
[CSSOM-1]
Daniel Glazman; Emilio Cobos Álvarez. CSS Object Model (CSSOM). URL: https://drafts.csswg.org/cssom/
[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[SELECTORS-4]
Elika Etemad; Tab Atkins Jr.. Selectors Level 4. URL: https://drafts.csswg.org/selectors/
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

Informative References

[CSS-BACKGROUNDS-3]
Elika Etemad; Brad Kemper. CSS Backgrounds and Borders Module Level 3. URL: https://drafts.csswg.org/css-backgrounds/
[CSS-COLOR-4]
Tab Atkins Jr.; Chris Lilley; Lea Verou. CSS Color Module Level 4. URL: https://drafts.csswg.org/css-color/
[CSS-CONTAIN-3]
Tab Atkins Jr.; Florian Rivoal; Miriam Suzanne. CSS Containment Module Level 3. URL: https://drafts.csswg.org/css-contain-3/
[CSS-SHADOW-PARTS-1]
Tab Atkins Jr.; Fergal Daly. CSS Shadow Parts. URL: https://drafts.csswg.org/css-shadow-parts/
[CSS21]
Bert Bos; et al. Cascading Style Sheets Level 2 Revision 1 (CSS 2.1) Specification. URL: https://drafts.csswg.org/css2/
[CSSOM-VIEW-1]
Simon Pieters. CSSOM View Module. URL: https://drafts.csswg.org/cssom-view/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/

Property Index

Name Value Initial Applies to Inh. %ages Anim­ation type Canonical order Com­puted value
anchor-default <anchor-element> implicit absolutely positioned elements no n/a discrete per grammar as specified
anchor-name none | <dashed-ident># none all elements that generate a principal box no n/a discrete per grammar as specified
inset-area none | <inset-area> none positioned elements with a default anchor element no n/a TBD per grammar as specified
position-animation normal | magic normal absolutely positioned elements no n/a (see prose) per grammar normal, or an overriding position rectangle (see prose)
position-try <'position-try-order'>? <'position-try-options'> see individual properties see individual properties see individual properties see individual properties see individual properties per grammar see individual properties
position-try-options none | [ [<dashed-ident> || <try-tactic>] | inset-area( <'inset-area'> ) ]# none absolutely-positioned elements no n/a discrete per grammar as specified
position-try-order normal | <try-size> normal absolutely positioned elements no n/a discrete per grammar as specified

IDL Index

[Exposed=Window]
interface CSSPositionTryRule : CSSRule {
  readonly attribute CSSOMString name;
  [SameObject, PutForwards=cssText] readonly attribute CSSStyleDeclaration style;
};

Issues Index

define the term that actually means this, matching ::before/after/backdrop/etc (but not ::marker/placeholder/etc whose box is not detectable).
Computed value for anchor() probably needs to be the anchor() function, but with the target anchor element resolved. This allows for transitions to work properly with tree-scoped names, and with changing anchor elements. See Issue 8180.
Do we need to control which box we’re referring to, so you can align to padding or content edge?
Define the precise timing of the snapshot: updated each frame, before style recalc.
Define how the values themselves are changed upon flipping: anchor(top) becomes anchor(bottom); start becomes end; etc.
Or should they cascade together?
What exactly are the constraints that determine what’s allowed here? Current list is based off of what’s reasonable from Chrome’s experimental impl. We can make a CQ that keys off of which fallback was used to allow more general styling, at least for descendants.
Would be useful to be able to detect when your anchor(s) are fully off-screen and suppress your display entirely. For example, tooltips living outside the scroller holding the text they’re anchored to don’t want to just hover over arbitrary parts of the page because their anchor happens to have that position relative to the scrollport.
Make sure the snapshotted scroll offset stuff is correct now, given interleaving.
Add conditions for forgetting the last successful position option, similar to the last remembered size
Nested anchors (an anchored element inside of another anchored element) present the potential for exponential blow-up of layouts when doing fallback, since the grandchild anchored element can cause scrollbars on an ancestor, changing the IMCB for the child anchored element, thus possibly causing the fallback choice to change for it.

There are strategies to avoid this, but they’re not without costs of their own. We should probably impose a maximum limit as well, to avoid this.

However, since *most* usages won’t be problematic in the first place, we don’t want to restrict them unduly just to prevent weird situations from exploding. Perhaps a complexity budget based on the branching factor at each level? Like, accumulate the product of the fallback list lengths from ancestors, and your fallback list gets limited to not exceed a total product of, say, 1k. Get too deep and you’re stuck with your first choice only! But this would allow large, complex fallback lists for top-level stuff, and even some reasonable nesting. (Length-five lists could be nested to depth of 4, for example, if we did go with 1k.)

More thought is needed.

Should the x/y offset be relative to the top/left corner or (CB) center to (AbsPos) center or something else?
Need a way to force animations to work correctly as well, like:
p {
    position-animation: magic;
    animation: foo 2s;
}

@keyframes foo {
  to {
    top: 0px;
    bottom: 100px;
    align-self: start;
  }
  from {
    top: 100px;
    bottom: 50px;
    align-self: end;
  }
}

Without some sort of magic intercept of animations (forcing them to turn into a position-animation animation) this will not smoothly animate.

match the concept of validity to whatever we do in similar situations.
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.
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?).