CSS Positioned Layout

Last updated:

[[As usual, this is a personal draft, and does not imply any recognition or acceptance by the CSS Working Group.]]

CSS 2.1 defines a position property which allows an author to position an element by specifying the distance between the element's edges and the edges of other boxes, rather than positioning the element according to the rules of its parent's layout model. However, the model was very restricted in what other boxes you could position an element relative to. This module allows an author to specify the box edges they want to position an element relative to much more generally, enabling a wide range of constraint-based layouts that cannot be done in traditional CSS layout models.

When using Positioned Layout, an element is removed from its normal position in the CSS box tree, leaving behind a placeholder with certain properties that depend on the positioning mode. The placeholder is then positioned as normal for the layout mode of its parent, while the element is instead positioned according to the rules of Positioned Layout.

Positioning Mode

property : position

values : static | relative | absolute | fixed

default value : static

applies to : all elements

The position property specifies the positioning behavior the element will exhibit.

A value of static means that the element is not using Positioned Layout at all - it must ignore the values of any of the positioning properties and be positioned according to its parent's layout model, without being removed from the box tree or generating a placeholder.

A value of relative means that the element's placeholder must participate in the layout model of the element's parent exactly as if it was the element itself. It acts as if it had the same values for any relevant layout properties on the element, and contained the element's children. If the value of any relevant layout properties on the element changes (for example, if a child's height is increased, increasing the element's height as well), the placeholder must respond in the same way.

A value of absolute means that the element's placeholder must act like a non-replaced inline element in the layout model of the element's parent with zero width and height.

A value of fixed means that the element's placeholder must act like a non-replaced inline element in the layout model of the element's parent with zero width and height. Additionally, once the position has been determined, it is treated as fixed relative to the viewport. If the viewport can scroll, the positioned element does not scroll with it. If there are multiple viewports (for example, in paged media each page is a viewport) the element appears in every viewport at the same fixed position relative to the viewport. [[Clarify anything else magical that fixpos stuff does.]]

Choosing the Positioning Edge

new property : position-root-top

values : [ <box-reference> [ top | bottom | auto ]? ] | <edge-reference>

default : auto auto

applies to : all elements

The position-root-top property specifies the reference edge that the element will be measuring its top value from (mutatis mutandi for right, bottom, and left).

This edge can be specified in two ways. The simpler method is to specify a reference box and an edge of that box. This is done by providing a value referring to the desired box, followed optionally by a reference to a side. For the <box-reference>, the following values are accepted:

  • self refers to the element's placeholder, left behind in the box tree position where the element itself would otherwise be.
  • previous refers to the element's previous sibling in the CSS element tree.
  • parent refers to the element's parent in the CSS element tree.
  • container refers to the containing block of the element, which may be the element's parent or a higher ancestor in the CSS element tree. [[Define here, define later, defer to CSS2.1?]]
  • root refers to the root element of the document the element is in.
  • viewport refers to the viewport. [[Define here, define later, defer to CSS2.1?]]
  • an <element> reference refers to the element specified, using the element() function. [[Expected to be defined in Values & Units, will be used in Image Values as well.]]
  • auto is interpreted differently based on the positioning mode. If position is static or relative, auto computes to self. If position is absolute, auto computes to container. If position is fixed, auto computes to viewport.

After the reference box value, a reference side can optionally be specified. In the properties position-root-top and position-root-bottom, the valid values are top, bottom, and auto. In the properties position-root-right and position-root-left, the valid values are left, right, and auto. If the value is omitted or auto, the edge used is the same as the position side being specified; that is, if auto is specified in position-root-top, it refers to the top edge, while if auto is specified in position-root-left, it refers to the left edge.

In some cases it is not sufficient to refer to a single edge; for example, an author may want to ensure that a box is positioned below selected boxes in three other columns. In this case an <edge-reference> may be used to automatically choose the correct edge out of a set of several edges.

An edge reference is specified by using one of the functions topmost(), bottommost(), leftmost(), or rightmost(). The syntax of these functions is:

topmost(    <single-edge-reference> [top|bottom]  
   [, <single-edge-reference> [top|bottom]]* )
bottommost( <single-edge-reference> [top|bottom]  
   [, <single-edge-reference> [top|bottom]]* )
rightmost(  <single-edge-reference> [right|left]  
   [, <single-edge-reference> [right|left]]* )
leftmost(   <single-edge-reference> [right|left]  
   [, <single-edge-reference> [right|left]]* )

<single-edge-reference> =
  [ self | previous | parent | container | root | viewport | <element> | <element-list> ]

(<element-list> is [[rather, should be]] defined in the Values and Units Module. You specify it with the elements() function.)

This suite of functions select the topmost (resp. bottommost, rightmost, leftmost) edge from all the edges passed as arguments to the function. The meaning of each argument is the same as described above, except for the new value <element-list>. When an <element-list> is used, it is equivalent to specifying all the elements represented by it as separate argument, using the same side reference.

Example: position-root-top: bottommost(elements(.last-in-columns) bottom); is equivalent to position-root-top: bottommost(element([selector for first .last-in-columns element]) bottom, element([second]) bottom, element([third]) bottom, ...);.

The topmost() and bottommost() functions are only valid in the position-root-top and position-root-bottom properties, while the rightmost() and leftmost() functions are only valid in the position-root-right and position-root-left properties.

new property : position-root

values : <position-root-top> [, <position-root-right> [, <position-root-bottom> [, <position-root-left>]?]?]?

default : auto auto

applies to : all elements

The position-root property is a shorthand allowing you to set all four position-root-* values at the same time.

Like other similar properties, if the left value is omitted it is the same as the right value, if the bottom value is omitted it is the same as the top value. If the right value is omitted it is usually the same as the top value: if the top value is specified with the first form (<box-reference> [top|bottom|auto]), the right value takes the same box-reference value, but takes a side reference of auto regardless of what the top value had; if the top value was an <edge-reference>, the right value's value is auto auto.

Preventing and Breaking Cycles in the Reference Graph

Layout models in general are composed of a carefully constructed set of implicit constraints that ensure that dependency cycles don't exist, or when they do exist, are broken in well-defined ways. Positioned Layout exposes these constraints directly, which creates the possibility of the author accidentally setting up arbitrary constraint cycles. Certain restrictions on the allowed rules can reduce the possibility of this, make resolving the cycles a tractable and predictable affair.

First, if positioning an element would cause another element to overflow, but the overflow behavior for the UA changes the layout of elements on the page such that the element no longer overlows, the UA must continue to act as if the element is overflowing, but inactivate any scrolling behavior.

Example: In a traditional desktop browser, when an element is in an overflow state it may generate scrollbars which reduce the size of the element's content area. If this size reduction would change the position of the element triggering overflow such that it no longer triggers overflow, then the browser will generate inactive scrollbars on the element in the overflow state. This makes the layout both stable and coherent.

Second, use of the element() and elements() function in the position-root-* properties is somewhat restricted. When element() or elements() is used, it must refer either to elements earlier in the document (ancestors or previous siblings) or to other positioned elements. [[Fix this: 'self' edges or 'auto' lengths don't really count as 'positioned', do they? Also, can I be more permissive here?]]

Containing the Positioned Element

[[ This section is just speculation right now. ]]

I want to address the case of 'frozen' table headers. You can almost do it with "thead { position: relative; position-root-top: bottommost(parent top, viewport top); }", but that will keep it on the screen when the table has scrolled completely off the top. Maybe make some way to specify a box that it can't go out of, or give another set of 4 edges that constitute a stronger set of position constraints. The first would be something like "position-contain: parent;", the latter would be maybe "position-restrict-bottom: parent bottom;". Is the flexibility of the latter worth the verbosity over the former? Is there a better solution to the frozen table headers problem?

Another use-case: ensuring that a tooltip doesn't extend outside of the viewport. Basically the same problem - you want to position it based on some element, but have a stronger positioning constraint that it not go outside the viewport box. Either solution from above works here too.

(a limited set of Markdown is supported)