CSS Flexible Flow Module

Abstract

This module defines the flow attribute and the flex unit. The two allow creating flexible layouts, which can fit in various view and content sizes.

The flow attribute and flex units address following problems that are not achievable in modern CSS:

1. Overview

Flexible layouts are defined by using flex length units and the flow attribute. Flex Length Units (flex units) allow to define dimensions, margins, and paddings of elements as portions of free space available inside the containing block. Flex value is a decimal number, appended with '*' (asterisk symbol) as a unit identifier.

The flow attribute defines the layout method of contained blocks in normal (position:static) flow. In other words, flow defines the layout manager of the container. This module contains definitions of the following standard layout managers:

  1. vertical
  2. horizontal
  3. horizontal-flow
  4. vertical-flow
  5. templated layout

Flex unit values can be thought of as tension in spring coils, which shift dimensions or position of elements, according to their "strength". Flex values per se:

Markup of the layout above:

<div class="container">
  <p>... some text ...</p>
</body>

where p has the style:

p
{
  width: 40%;             /* fixed width - 40% of width of the container */
  margin-left: 2*;        /* left "spring" of power 2 */
  margin-right: 1*;       /* right "spring" of power 1 */
  border:1px solid black; /* border of fixed width */
}

2. Flex length units

Dimensions of elements, defined in flex units, are computed against free space available inside the containing box.

Computation of flex unit values is made after all non-flex values - the final step of the layout algorithm. It's possible that some space inside the container will be left undistributed during this step. That space can be alloted among all flex values competing for free space in vertical or horizontal directions in the container.

Flex units are only applicable to padding, margin, width, and height CSS attributes of elements. They are also only determined for statically (normal flow), and absolutely positioned elements. Floated elements do not support flex units - attributes given in flex units shall be evaluated to auto value.

While computing the final dimensions of elements, a flex unit value is interpreted as a weight. If the sum of flex values competing for free space is less than 1*, a corresponding portion will remain undistributed. If the sum of flexes is greater than or equal to 1*, all free space will be allocated by using flex values as weights of proportional distribution.

For example, the following styles:

#container { width:300px; }
#element { width:1*;
           margin-left:2*;
           margin-right:1*;
           border:2px solid;
           padding:0; }

applied to the #element inside the #container will lead to following dimensions of the #element:

sum-of-flexes = 1* + 2* + 1* = 4*;
width-to-distribute = 300px - 2px - 2px; // container width minus fixed borders
width        = 1/4 * width-to-distribute = 74px;
margin-right = 1/4 * width-to-distribute = 74px;
margin-left  = 2/4 * width-to-distribute = 148px;

Flex unit computations respect all usual constraints. For example, min-width and max-width define boundaries where the width above can "flex". For flex unit computation purposes, the initial (default) value of  min-width attribute is being interpreted as having minimal intrinsic value.

2.1. min-intrinsic, max-intrinsic length values.

CSS properties min-width, max-width, width and min-height, max-height, height additionally may accept special values: 'min-intrinsic' and 'max-intrinsic'.

width:min-intrinsic for paragraph <p>first second</p> will be equal to the width of widest word in the paragraph. width:max-intrinsic for the paragraph will be equal to the sum of all words and spaces in the paragraph - the paragraph will be rendered as single line of text.

2.2. "Relative flexes", the fx() function.

The fx() function allows to define flexible length with a base value. There are two forms of the fx function:

  1. fx( <decimal> | <percent> )  - this form defines plain (a.k.a absolute) flex value. fx(1) is an exact equivalent of 1*  and fx(50%) is just another form of defining 0.5*.
  2. fx( <decimal> | <percent>, <length> | <percent> ) - this form defines flexible length that is "flexing around" the base value.

Example: these two children that use relative flexes:

<div style="flow:horizontal; width:440px">
  <div style="width:fx(1,160px)" />
  <div style="width:fx(1,80px)" />
</div>

will be rendered using following computed lengths:

To compute relative flex values we susbstract all base lengths from free space of the container and distribute only space that is left after that (a.k.a. post-base space). The post-base space can be as positive as negative. If post-base space is negative then final lengths will be less than their base values.

The fx() function can be used in the same set of properties where flex units are acceptable.

3. The flow attribute

The flow attribute defines how the container lays out its children. In other words, it establishes a layout manager for the container element.

'flow'

Value:   default | horizontal | vertical | horizontal-flow | vertical-flow | "template" | row(...) | inherit
Initial:   default
Applies to: box elements with display model "blocks inside"
Inherited:   no
Percentages:   no
Media:   visual
Computed value:   specified value

This attribute defines the layout method of children in normal flow:

value of the flow Resulting display of children
vertical Stacked vertically.
horizontal Positioned horizontally in a single row. Direction is governed by the direction attribute, in particular by values ltr and rtl.
horizontal-flow Positioned horizontally, wrapping on multiple rows, if needed. Direction is governed by the direction attribute.
vertical-flow Positioned vertically, wrapping on multiple columns, if needed.
"template" Replaced according to the template. Binding of child position with the named cell in the template is made by using the float:"name" attribute.
row(tag1,tag2,...) Positioned in rows according to the row() template function.

3.1. flow:vertical

Flow vertical is close to standard top-to-bottom layout of block elements, such as div, ul, and others. The only difference is in the use of flex units by flowed elements.

All static child elements of the flow:vertical container are replaced from top to bottom, one by one, forming a single column spanning the width of the container. Horizontal dimensions of the contained elements, defined in flex units, are computed against the width of the container. Similarly, vertical dimensions of contained elements are computed against the height of the container. If the container height is not defined, or defined as height:auto, there is no free space to distribute among flex values. Thus, the flex computation in that direction can be ommited.

If the container has its height defined, and that height is greater than min-intrinsic height of the contained elements, then there is a free space. In such a container, this space is distributed among the vertical flex dimensions of the contained elements.

For example, the following styles:

  #container { height:100%; border:1px dotted; }
  #first { margin-bottom:1*; }

when defined for the markup:

  <div id="container">
    <h2>Alignment to top/bottom</h2>
    <div id="first" style="margin-bottom:1*">
      <code>margin-bottom:1*;</code>
      <p>Shifts rest to the bottom</p>
    </div>
    <div id="second">
      Normal div
    </div>
  </div>

will position the first element at the top of the #container, and the #second element at its bottom.

3.1.1. Margin collapsing in flow:vertical containers

Vertical margins of contained elements collapse as usual in CSS.  The only difference appears if one of the collapsing vertical margins has a flex value, and the corresponding margin of the other element has fixed value. In that case, that fixed value establishes a "min-constraint" for the flex computation of the margin between these two elements. Such a margin is flexible, but not less than the fixed value.

3.2. flow:horizontal

This is a single row layout. All static child elements of flow:horizontal container are replaced horizontally one by one, forming a single row. Layout is performed with respect to the direction attribute of the container.

In the horizontal direction, width, left and right margins, borders and paddings of contained elements, when given in flex units, participate in free space distribution. All immediate children of the flow:horizontal container are competing for free space available between the left and right sides of the container's content box. Thus, this free space is distributed among them, proportionally to corresponding flex values.

In the vertical direction: flex values of height, top and bottom margins, borders and paddings of contained elements are computed against the height of the container. This makes it possible to align elements of the flow:horizontal container not only horizontally, but also vertically.

All children have the same height with this style:

#container       { flow:horizontal; border-spacing:4px; padding:4px; }
#container > div { margin:0; height:1*; }

The rendering will look like:

Here, all children have their intrinsic height. However, the top margin is set to 1*, shifting boxes to the bottom:

#container       { flow:horizontal; border-spacing:4px; padding:4px; }
#container > div { margin-top:1*; height:auto; }
                               /* that is "intrinsic" height */

3.2.1. Margin collapsing in flow:horizontal containers

Horizontal margins of contained block elements collapse in the same way vertical margins of flow:vertical containers do. With its in-flow children, margins of flow:horizontal container do not collapse.

3.2.2. Intrinsic dimensions

Intrinsic height of flow:horizontal container is the height of the margin box of the tallest ellement in the row. Intrinsic width of flow:horizontal container is a sum of intrinsic dimensions of the contained elements with respect to the horizontal margins collapsing.

3.3. flow:horizontal-flow

flow:horizontal-flow is a variation of flow:horizontal layout. It contains blocks which are allowed to wrap if there is not enough space in the horizontal direction inside the container.

The attribute clear:left|right|both allows to break the flow of elements into multiple rows explicitly.

The row will wrap if any of the base conditions are met:

  1. clear:left|right|both is used on the correspondent element;
  2. there is not enough horizontal space in the row to replace the element.

In the vertical direction, flex values of height, top and bottom margins, borders and paddings of contained elements, are computed against the height of the current row. The height of the row is equal to the height of the tallest element in the row, calculated without the influence of flex units.

In the horizontal direction, flex values of the elements in rows are computed exactly as in flow:horizontal.

For example, the following markup:

<div style="flow:horizontal-flow" >
  <div style="width:100px" > width:100px </div>
  <div style="width:1*"    > flexible width:1*
                             flexible width:1* </div>
  <div style="width:1*"    > flexible width:1*
   flexible width:1*
                             flexible width:1* </div>
  <div style="width:150px" > width:150px</div>
</div>

will be rendered as:

3.4. flow:vertical-flow

flow:vertical-flow is a multi-column layout that is similar to flow:horizontal-flow, but in the vertical direction. Elements are replaced from top to bottom. If there is not enough vertical space inside the container, the elements will wrap, forming as many columns as needed.

The attribute clear:left|right|both allows to break the flow of elements into multiple columns explicitly.

The column will wrap if any of the base conditions are met:

  1. the sum of flexes in the column in the vertical direction becomes greater than 1*
  2. clear:left|right|both is used on the corresponding element
  3. not enough vertical space is available in the column to replace next element

In the horizontal direction, flex values of width, left and right margins, and paddings of contained elements, are computed against the width of the current column. The width of the row is equal to the width of the widest element in the column, calculated without influence of flex units.

In the vertical direction, flex values of the elements in columns are computed exactly as in flow:vertical.

For example, in the following markup:

<ul style="flow:vertical-flow">
    <li style="height:150px" > 1. height:150px </li>
    <li style="height:100px" > 2. height:100px</li>
    <li style="height:0.3*"    > 3. flexible height:0.3*
                                    flexible height:0.3* </li>
    <li style="height:0.7*"    > 4. flexible height:0.7*
                                    flexible height:0.7*
                                    flexible height:0.7* </li>
    <li style="height:150px" > 5. height:150px</li>
    <li style="height:150px" > 6. height:150px</li>
    <li style="height:150px" > 7. height:150px</li>
  </ul>

each list item is styled as having width:150px. This will produce the following layout, where these list items are wrapped into three columns:

3.5. flow:"template"

Note that this is simplified version of http://www.w3.org/TR/css3-layout/. All credits for the idea go to authors of that document.

flow: <template-expression> allows to replace elements according to the template expression.

Here, the template expression is a sequence of string literals. Each such string literal is a whitespace-separated list of name tokens, where each name designates a cell in the grid. Multiple cells may have the same name. In this case, the name defines a placeholder that spans multiple cells of the grid.

For example, the following template defines a 3x4 grid of cells with 6 placeholders named from "a" to "f". Some placeholders span multiple cells of the grid:

flow: "a a a"
      "b c e"
      "d c e"
      "d c f";

Each child of the element that has such a template defined can be bound with a particular named placeholder by using float:"placeholder-name" attribute:

li:nth-child(1) { float:"a"; }
li:nth-child(2) { float:"b";
                  width:150px;
                  height:max-intrinsic; }
li:nth-child(3) { float:"c";
                  width:*; height:*; } /* flexes, a.k.a. shrink-to-fit */
li:nth-child(4) { float:"d";
                  width:150px;
                  height:*; }
li:nth-child(5) { float:"e";
                  width:150px;
                  height:*; }
li:nth-child(6) { float:"f";
                  width:150px;
                  height:150px; }

Note about reuse of the float attribute: float:left|right attribute is not supported for immediate children of the flow container. In other words flowed elements can float (be replaced) only to designated cells defined by the flow:"template".

All non-bound children of the templated container are appended to the grid as if they span a single row in it. If there are multiple children with the same placeholder name, only the first element (in DOM order) will be bound with the placeholder. The rest of the elements will be replaced as unbound.

A placeholder name can only span rectangular, and unique, areas of cells in the template. Otherwise, the whole template is invalid, and the computed value of the flow shall be interpreted as flow:default.

For example, this markup:

<ul>
    <li> "a", width:auto (that is 1*), height:auto(that is max-intrinsic) </li>
    <li> "b", width:150px, height:max-intrinsic </li>
    <li> "c", width:*, height:* (a.k.a. shrink-to-fit) </li>
    <li> "d", width:150px, height:* </li>
    <li> "e", width:150px, height:* </li>
    <li> "f", width:150, height:150px</li>
 </ul>

with styles defined above will be rendered as:

sample:template

Instead of characters defining "names" of child elements the template may contain ordinal numbers of children of the templated container. So this template

flow: "1 1"
"2 3";

will cause first three children of the container to be laid out in two rows where first element will span entire first row and second and third will be replaced in second row.

3.6. flow: row(tag1, tag2, ...)

The flow:row() is used to replace elements in table alike layouts. The row() function contains list of tag names of elements that will be placed in single row and in corresponding columns ot the table.

Consider this markup:

<dl><dt>First</dt>
    <dd>Description of the first item</dd>
<dt>Second</dt> <dd>Description of the second item</dd></dl>

and this style declaration:

dl { flow: row(dt,dd); }

It will be rendered as if items were placed in table like this:

First Description of first item
Second Description of second item

If the flow:row(...) element contains elements that do not match the row template then such elements will be placed in separate row spanning all available columns, so this markup:

<dl>
  <header>Group</header>    
    <dt>First</dt>    
    <dd>Description of the first item</dd>
<dt>Second</dt> <dd>Description of the second item</dd> <header>Another group</header> <dt>Third</dt> <dd>Description of the third item</dd>
</dl>

will be rendered as:

Group
First Description of the first item
Second Description of the second item
Another group
Third Description of the third item

4. The flow, float and block formatting context

Elements that are immediate children of elements with a non-default flow attribute value, establish new block formatting contexts exactly in the same way as cells in tables do.

5. The flow and position

The flowed element is positioned statically in its flow container. This means that the values position: absolute | fixed on such an element must be treated as position:static.

position:relative is allowed on flowed elemets. Therefore, the element can be offset from its static position by using left, right, bottom and top attributes.

5.1 position: absolute | fixed and the flex units

sample:position-fixedFlex units can be used as values of left, top, right and bottom attributes of elements having position:absolute or position:fixed defined. Combined with possible flex values of padding, margin, width, and height on these elements it makes possible to align such elements relative to their containing blocks.

Example: following style will place the element #light-box-dialog in the middle of the viewport:

#light-box-dialog
{
  position: fixed;
  left:1*; top:1*; right:1*; bottom:1*;
  width: 400px;
  height: auto;
}

The #light-box-dialog element will have width 400px, height: auto (that is height:min-intrinsic) and will be placed in the middle of the viewport.

6. The flow and vertical-align

Flowed elements establish block formatting contexts. Thus, their vertical-align attribute defines the vertical alignment inside them, rather than vertical alignment of the block itself. In other words, vertical-align interpretation is the same as for table cells.

7. The flow and border-spacing

The border-spacing attribute defines the minimal value of vertical and horizontal margins between flowed elements. If a flowed element has its own defined margins, the used value for the margins is the maximum between the border-spacing, and the defined margin values. If the defined margins use a flex unit value, the flex computation uses fixed value of the border-spacing as a minimal constraint. In this case, the margin is computed using the flex algorithm, but it can't be less than the value of the border-spacing attribute.

8. The flow and margin collapsing

Margin collapsing between flowed elements is defined for each layout manager above. Margins of flow container elements do not collapse with their in-flow children.

9. The flow and inline-block elements

Inline block elements are replaced inside corresponding line boxes. In principle, line boxes establish bounds where elements replaced in them can "flex".

Therefore, inline-block elements such as <img>, <input>, and <span style="display:inline-block"> can use flex units to define their dimensions, margins, and paddings. Calculations of flex units in the line box context are made against vertical and horizontal dimensions of the line box, and its non-flexible content.

In the horizontal direction, it's possible that free horizontal space remains in the line box after replacing all non-flexible content (e.g. word boxes). That space gets distributed among elements having width, left, and right margins (or paddings), given in flex units.

In the vertical direction, the flex values of height, top and bottom margins, and paddings of inline-block elements, are computed against the height of the line box. For example this makes it possible to define multiple child elements with the heights equal to that of the line box.

If defined, the text-align:justify computation is performed after the flex value computation.

For example, the following markup:

<style>
  p
  {
    padding:4px; border:2px solid black; line-height:1.8em;
  }
  span
  {
    display:inline-block; border: 2px solid salmon;
    background:seashell;
  }
</style>
<p>
   First span:<span style="width:2*">width:2*</span>
   and second one:<span style="width:1*">width:1*</span>
</p>

will produce these layouts for various widths of the p element:

Note that on the last image last span has reached its min-intrinsic width thus its width is excluded from flex units computation.

10. Algorithm of flex units computation.

spring-engine.h (C++ source) is an implementation of the algorithm used for computation of system of flex values.

The algorithm has clear physical interpretation:
It calculates final lengths of a set of coil springs attached one by one to each other. Each spring in the set has its own strength value (flex unit) and min/max limiters ( min/max constriants ).

Non-flexible element can be thought as element with no flexibility and the only min-constraint given. So the implementation can be used to compute distribution of systems of fixed and flexible elements.


Demo:

Micro-browser application demonstrating the flow attribute and flex units that was used for creating screenshots above is available at http://www.terrainformatica.com/w3/demo-w3c.zip (Executable for Windows and Linux under the Wine).

Authors:

Andrew Fedoniouk
andrewf@terrainformatica.com, Terra Informatica Software, Inc.
Ivan Goroun
ivang@terrainformatica.com, Terra Informatica Software, Inc.

Date: April, 5, 2009.

Updated on: February, 25, 2012.