CSS Masonry (alt proposal)

Unofficial Proposal Draft,

More details about this document
This version:
http://tabatkins.github.io/specs/css-masonry/
Issue Tracking:
CSSWG Issues Repository
Inline In Spec
Editor:
Tab Atkins-Bittner
Suggest an Edit for this Spec:
GitHub Editor

Abstract

An alternate syntax proposal for the Masonry feature currently defined in Grid 3.

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 section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.

Please send feedback by filing issues in GitHub (preferred), including the spec code “css-masonry” in the title, like this: “[css-masonry] …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.

This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

1. Introduction

"Masonry" is the common name for a form of layout that’s somewhat "halfway" between Flexbox and Grid layout: you pre-define some tracks in one axis (either rows or columns), and child elements flow into those tracks, automatically distributing themselves across them to keep the "heights" of the tracks as similar as possible.

The end result is similar to an auto-flowed Grid, but "packed tighter", rather than requiring the elements to line up in both rows and columns. Alternately, it’s similar to a Flexbox, except the "lines" are pre-defined and the items automatically distribute themselves across them, rather than filling one line at a time until it’s full.

This proposal is intentionally just a sketch of the full spec; for the most part, the final spec will closely resemble that of the Grid spec [css-grid-1], so any "missing" parts should be assumed to be identical to Grid, mutatis mutandi.

2. Masonry Containers

2.1. Establishing Masonry Containers: the masonry and inline-masonry display values

Name: display
New values: masonry | inline-masonry
masonry

This value causes an element to generate a masonry container box that is block-level when placed in flow layout.

inline-masonry

This value causes an element to generate a masonry container box that is inline-level when placed in flow layout.

3. Defining the Masonry

A masonry container contains a masonry grid: a set of one or more masonry tracks, which masonry items are flowed into.

The direction that the masonry tracks extend is the masonry container’s main axis (the block axis for column masonry containers; the inline axis for row masonry containersd), and the perpendicular direction, which tracks are stacked in, is the cross axis.

3.1. Defining Tracks: the masonry-template-tracks property

Name: masonry-template-tracks
Value: <masonry-track-list> | <masonry-auto-track-list> | <masonry-intrinsic-track-list> | subgrid <line-name-list>?
Initial: repeat(auto-areas, auto)
Applies to: masonry containers
Inherited: no
Percentages: n/a
Computed value: by computed value type per item in the computed track list
Canonical order: per grammar
Animation type: if list lengths match, by computed value type; otherwise, discrete

The masonry-template-tracks property defines, as a space-separated track list, the line names and track sizing functions of the masonry.

<masonry-track-list> =
  [ <line-names>? <masonry-track-component> ]+ <line-names>?
<masonry-track-component> =
  <extrinsic-track-size> | <extrinsic-fixed-repeat>
<extrinsic-track-size> =
  <extrinsic-breadth> | minmax( <fixed-breadth>, <extrinsic-breadth> )
<extrinsic-breadth> =
  <length-percentage [0,∞]> | <flex [0,∞]>
<extrinsic-fixed-repeat> =
  repeat( <integer [1,∞]>, [ <line-names>? <extrinsic-track-size> ]+ <line-names>? )

<masonry-auto-track-list> =
  [ <line-names> | <masonry-track-list> ]?
  <extrinsic-auto-repeat>
  [ <line-names> | <masonry-track-list> ]?
<extrinsic-auto-repeat> =
  repeat( [ auto-areas | auto-fill ] , [ <line-names>? <extrinsic-track-size> ]+ <line-names>? )

<masonry-intrinsic-track-list> =
  <line-names>? <masonry-intrinsic-repeat> <line-names>?
<masonry-intrinsic-repeat> =
  repeat( [ <integer [1,∞]> | auto-areas | auto-fill ] , <line-names>? <intrinsic-track-size> <line-names>? )
<intrinsic-track-size> = <intrinsic-breadth> | minmax( <inflexible-breadth>, <intrinsic-breadth> ) | fit-content( <length-percentage [0,∞]> )
<intrinsic-breadth> = auto | min-content | max-content
The complex grammar above is much easier to understand in natural language.

Your track list can be any combination of:

Alternately, it can be a single repeat() (with any repetition count) using intrinsic sizes, like auto or min-content.

Or as a third option, it can be a subgrid keyword, which takes tracks from its parent grid container or masonry container according to its span.

While similar to grid-template-columns/grid-template-rows, masonry-template-tracks has a slightly different set of allowed values.

Grid can use:

While Masonry can use:

The subgrid value indicates that the masonry will adopt the spanned portion of its parent grid or parent masonry.

3.1.1. Track Sizes

<length-percentage [0,∞]>

A non-negative length of percentage, as defined by CSS Values. [css-values-3]

<percentage> values are relative to the inner cross size of the masonry container. If the size of the masonry container depends on the size of its tracks, the the <percentage> must be treated as the default masonry size, for the purpose of calculating the intrinsic sizes of the masonry container, and then resolve against that resulting masonry container size for the purpose of laying out the masonry and its items.

<flex [0,∞]>

A non-negative dimension with the unit fr specifying the track’s flex factor. Each <flex>-sized track takes a share of the remaining space in proportion to its flex factor. For example, given a track listing of 1fr 2fr, the tracks will take up 1/3 and 2/3 of the leftover space, respectively.

minmax(min, max)

Defines a size range greater than or equal to min and less than or equal to max. If the max is less than the min, then the max will be floored by the min (essentially yielding minmax(|min|, |min|).)

As a maximum, a <flex> value sets the track’s flex factor; it is invalid as a minimum.

auto

As a maximum: represents the largest max-content contribution among all the masonry items, corrected for spanning; but unlike max-content, allows expansion of the track by the align-content and justify-content properties.

As a minimum: represents the largest minimum size among all the masonry items, corrected for spanning. (This initially is often, but not always, equal to a min-content minimum—​see XXX.)

When appearing outside a minmax() notation: equivalent to minmax(auto, auto), representin the range between the minimum and maximum described above. (This behaves similarly to minmax(min-content, max-content) in the most basic cases, but with extra abilities.)

max-content

Represents the largest max-content contribution among all the masonry items, corrected for spanning.

min-content

Represents the largest min-content contribution among all the masonry items, corrected for spanning.

fit-content( <length-percentage> )

...

To correct for spanning, find the specified intrinsic size of the masonry item, subtract the sizes of the gaps it crosses, then divide by its span.

Note: For single-track items, correcting for spanning has no effect.

The default masonry size is the size that a masonry track would be if the masonry had masonry-template-tracks: repeat(N, auto), where N is the number of tracks that the masonry container has.

3.1.2. Repeating Tracks: the repeat() notation

There are two distinct syntaxes for repeat() in Masonry:

An <integer> repeat simply represents the same thing as repeating the tracks specified by the second argument. For example, repeat(2, 1em 300px) is identical to writing 1em 300px 1em 300px.

An auto-fill repeat represents repeating the second argument the largest whole number of times that wouldn’t cause the masonry grid (including tracks not in the repeat(), gaps, etc) to overflow the content box of the masonry container (minimum of one repetition).

An auto-areas repeat depends on the value of masonry-template-areas:

For example, given masonry-template-areas: "a a a b b";, a repeat(auto-areas, 100px 20px) is equivalent to specifying 100px 20px 100px 20px 100px: two whole repeats and one partial repeat, to match up with the five tracks specified by the areas.

3.2. Named Areas: the masonry-template-areas property

Name: masonry-template-areas
Value: none | <string>
Initial: none
Applies to: masonry containers
Inherited: no
Percentages: n/a
Computed value: the keyword none or a string
Canonical order: per grammar
Animation type: discrete

This property specifies named masonry areas, which can be referenced from the masonry-placement properties.

Values have the following meanings:

none

Indicates no named masonry areas are defined by this property.

Note: Named masonry areas can still be created implicitly, by manually naming lines like foo-start and foo-end to create a foo area between them.

<string>

The string is parsed into a number of cells, as follows:

  1. Tokenize the string into a list of the following tokens, using longest-match semantics:

    • A sequence of ident code points, representing a named cell token with a name consisting of its code points.

    • A sequence of one or more "." (U+002E FULL STOP), representing a null cell token.

    • A sequence of whitespace, representing nothing (does not produce a token).

    • A sequence of any other code points, representing a trash token.

If the string doesn’t define at least one cell token, the declaration is invalid. If the string defines multiple named areas with the same name, the declaration is invalid.

Defined areas give implicit-assigned names to grid lines in the masonry container.

3.3. Masonry Track Direction: the masonry-direction property

Name: masonry-direction
Value: row | column | row-reverse | column-reverse
Initial: column
Applies to: masonry containers
Inherited: no
Percentages: n/a
Computed value: as specified
Canonical order: per grammar
Animation type: discrete

The masonry-direction property specifies how masonry items are placed in the masonry container, by setting the direction of the masonry container’s main axis, and its main-start and main-end directions.

column

The masonry container’s main axis is its block axis, and its main-start direction is its block-start.

column-reverse

The masonry container’s main axis is its block axis, and its main-start direction is its block-end.

row

The masonry container’s main axis is its inline axis, and its main-start direction is its inline-start.

row-reverse

The masonry container’s main axis is its inline axis, and its main-start direction is its inline-end.

The masonry container’s cross axis is perpendicular to its main axis; its cross-start direction is determined by masonry-fill. The main-end and cross-end directions are opposite the main-start and cross-start directions.

A masonry container is a column masonry container if its main axis is the block axis, or a row masonry container if its main axis is the inline axis.

3.4. Masonry Cross Direction: the masonry-fill property

Name: masonry-fill
Value: normal | reverse
Initial: normal
Applies to: masonry containers
Inherited: no
Percentages: n/a
Computed value: as specified
Canonical order: per grammar
Animation type: discrete

masonry-fill determines what direction masonry items are laid out in when multiple tracks are tied for "shortest".

normal

The masonry container’s cross-start direction is its inline-start or block-start direction (whichever is in its cross axis). (This means that ties are broken by filling from start to end.)

reverse

The masonry container’s cross-start direction is its inline-end or block-end direction, instead.

3.5. Masonry Flow Directions: the masonry-flow property

Name: masonry-flow
Value: <'masonry-direction'> || <'masonry-fill'>
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

The masonry-flow property is a shorthand for the masonry-direction and masonry-fill properties.

3.6. Masonry Definition Shorthand: the masonry property

Name: masonry-template
Value: <'masonry-template-areas'> || <'masonry-template-tracks'> || <'masonry-direction'> || <'masonry-fill'>
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

The masonry-template shorthand sets masonry-template-areas, masonry-template-tracks, and masonry-direction at the same time.

4. Placing Grid Items

masonry-track, masonry-track-start, masonry-track-end, and masonry-area, with the obvious definitions matching grid-row/etc.

Spans are clamped at layout time to the defined masonry tracks; there is no "implicit grid".

4.1. Precision During Filling: the masonry-slack property

Name: masonry-slack
Value: <length-percentage>
Initial: 1em
Applies to: masonry containers
Inherited: no
Percentages: relative to the main axis content box size of the masonry container
Computed value: a computed length
Canonical order: per grammar
Animation type: discrete

Masonry containers are filled by placing each masonry item in whichever masonry track is currently the least filled. When multiple tracks are tied for least-filled, placing the items in order looks good. But if tracks are only very slightly different heights, it can look strange to have them not fill in order, as the height differences aren’t perceived as meaningfully different.

masonry-slack specifies what the threshold is for considering tracks to be "the same height", causing them to fill in order.

<length>

Specifies the tie threshold for the masonry container. Placement positions are considered to be equally good ("ties") if they are within the specified distance from the shortest position.

Note: The initial value is a "small" distance (1em) that is probably appropriate to represent "close enough".

Is 1em the right default?

Do we want a dense option, a la grid-auto-flow? It’s much more expensive in Masonry, as you have to lay out the element in every possible gap spot to see if it’s short enough to fit; Grid gets to just juggle some integers. Leaving it out for now; we can add it later if needed.

4.2. Masonry Placement Algorithm

Masonry distributes its items as evenly as possible among its tracks, always choosing the least-filled track to place the next item into.

An important property that this distribution maintains is that it always "moves forward" when there are ties: If multiple tracks are tied for shortest, it will still evenly distribute items to them one-by-one, even if they remain tied after each item is placed (because the item is zero height, or the tie threshold is a large value).

The following masonry placement algorithm determines the position of all the masonry items in a masonry container, distributing them across the masonry tracks according to whichever is the least-filled.

Note: This algorithm refers to the "index" of tracks, and talks about indexes being "smaller" or "larger" than each other; this implies numbering them from the cross-start side to the cross-end side (and is thus dependent on masonry-fill).

  1. Generate anonymous masonry items.

  2. For each masonry track, maintain a fill height, initially 0px for all tracks.

  3. Let placement cursor be an index into the track list, initially pointing to the first track.

  4. For each masonry item item in order-modified document order:

    1. Let chosen start track be an index into the track list, initially unset.

    2. If item has a definite track position, set chosen start track to that position.

      Otherwise, choose a start track, given item, the masonry tracks and their fill heights, the masonry container’s tie threshold, and placement cursor, and set chosen start track to the result.

    3. Let spanned tracks be the chosen start track plus following tracks according to item’s span.

    4. Lay out item into spanned tracks, as specified in .

      Let item height be the main size of item after layout.

    5. Let max fill height be the largest fill height among spanned tracks.

      Update all of spanned tracks’s fill heights to be max fill height + item height.

    6. If item did not have a definition track position initially, set placement cursor to the first track following spanned tracks; if there are no tracks following spanned tracks, set it to the first track. (Definite-position items don’t update the cursor.)

Note: This algorithm chooses the track that would result in the item being placed as highly as possible. If there are ties, it chooses the earliest such track, after the most recently placed item if possible.

To choose a start track given:

  1. Let extra span be item’s span - 1.

  2. Let possible start tracks be all but the last N tracks, where N is extra span.

  3. Let each item of possible start tracks have a spanning fill height, which is the largest fill height among the track itself and the next extra span tracks.

  4. Let min fill height be the smallest spanning fill height among tracks.

  5. Let choosable tracks be all the possible start tracks whose spanning fill height is within threshold of min fill height.

  6. If any of the choosable tracks indexes are equal or greater than placement cursor, remove any items from choosable tracks whose index is smaller than placement cursor.

  7. Return the item of chooseable tracks with the smallest index.

5. Alignment and Spacing

Like Grid Layout [css-grid-1], the align-* properties align in the masonry container’s block axis, and justify-* in the inline axis (rather than being based on the main axis and cross axis, like in Flexbox).

For column masonry containers:

justify-content on the masonry container

The alignment subjects are the masonry tracks. The alignment container is the masonry container’s context box.

align-content on the masonry container

The alignment subjects are runs of consecutive single-track masonry items in a given track between the start/end of the track or spanning items. The alignment container is the available space in the track between the two things bounding the run. (See Issue 10275.)

justify-self on the masonry items

The alignment subject is the masonry item. The alignment container is the available space in the masonry area the item is placed in.

align-self on the masonry items

No effect.

For row masonry containers, swap all the axises above.

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.

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-BOX-4]
Elika Etemad. CSS Box Model Module Level 4. URL: https://drafts.csswg.org/css-box-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-DISPLAY-4]
CSS Display Module Level 4. Editor's Draft. URL: https://drafts.csswg.org/css-display-4/
[CSS-FLEXBOX-1]
Tab Atkins Jr.; et al. CSS Flexible Box Layout Module Level 1. URL: https://drafts.csswg.org/css-flexbox-1/
[CSS-GRID-2]
Tab Atkins Jr.; Elika Etemad; Rossen Atanassov. CSS Grid Layout Module Level 2. URL: https://drafts.csswg.org/css-grid-2/
[CSS-SIZING-3]
Tab Atkins Jr.; Elika Etemad. CSS Box Sizing Module Level 3. URL: https://drafts.csswg.org/css-sizing-3/
[CSS-SYNTAX-3]
Tab Atkins Jr.; Simon Sapin. CSS Syntax Module Level 3. URL: https://drafts.csswg.org/css-syntax/
[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/
[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

Informative References

[CSS-GRID-1]
Tab Atkins Jr.; et al. CSS Grid Layout Module Level 1. URL: https://drafts.csswg.org/css-grid/
[CSS-VALUES-3]
Tab Atkins Jr.; Elika Etemad. CSS Values and Units Module Level 3. URL: https://drafts.csswg.org/css-values-3/

Property Index

Name Value Initial Applies to Inh. %ages Anim­ation type Canonical order Com­puted value
masonry-direction row | column | row-reverse | column-reverse column masonry containers no n/a discrete per grammar as specified
masonry-fill normal | reverse normal masonry containers no n/a discrete per grammar as specified
masonry-flow <'masonry-direction'> || <'masonry-fill'> see individual properties see individual properties see individual properties see individual properties see individual properties per grammar see individual properties
masonry-slack <length-percentage> 1em masonry containers no relative to the main axis content box size of the masonry container discrete per grammar a computed length
masonry-template <'masonry-template-areas'> || <'masonry-template-tracks'> || <'masonry-direction'> || <'masonry-fill'> see individual properties see individual properties see individual properties see individual properties see individual properties per grammar see individual properties
masonry-template-areas none | <string> none masonry containers no n/a discrete per grammar the keyword none or a string
masonry-template-tracks <masonry-track-list> | <masonry-auto-track-list> | <masonry-intrinsic-track-list> | subgrid <line-name-list>? repeat(auto-areas, auto) masonry containers no n/a if list lengths match, by computed value type; otherwise, discrete per grammar by computed value type per item in the computed track list

Issues Index

Is 1em the right default?
Do we want a dense option, a la grid-auto-flow? It’s much more expensive in Masonry, as you have to lay out the element in every possible gap spot to see if it’s short enough to fit; Grid gets to just juggle some integers. Leaving it out for now; we can add it later if needed.