Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Masonry

Cards of unequal height packed into columns, each card under whichever column is shortest.

By the end of this chapter you can build a wall of cards that survives a window being dragged narrower, choose between a fixed count and a minimum width, and know why the wall settles a frame late.

masonry

A masonry deals its children into columns and places each one under the column that is currently shortest.

masonry id="wall" min-column-width=320 {
    card { text class="card-title" "Leagues"; statistic label="Walked" value="1,795" }
    card { text class="card-title" "The Company"; text "Nine set out from Rivendell." }
    card { text class="card-title" "Provisions"; text "Lembas, 30 days." }
}
var wall = new Masonry(List.of(leagues, company, provisions))
        .withAttributes(Attributes.NONE.id("wall"));

new Masonry(List<Widget>) is responsive at 320 points. new Masonry(List<Widget>, int columns, Attributes) is a fixed count. columns(int) and minColumnWidth(int) switch a masonry from one mode to the other, dropping the setting it had.

This is the one container the catalogue cannot write in flexbox. A wrapping row aligns its lines, and a column per column cannot balance them, because balancing needs every item’s height and those are not known until they are laid out.

How it places

It reads last frame. Every cell reports what it came out as through Measured, the state banks the height, and the next frame puts each card under the column that is shortest. The first frame has no measurements, so a responsive wall is one column and the second frame is right. That is one frame of settling nobody sees.

Reading last frame is allowed here because moving a card between columns does not change its height. The columns are the same width, so the number being reported is stable under the thing it causes. That is also why columns is a count and not a list of widths.

A responsive wall counts its columns from its own width, banked the same way. With n columns needing n minimums and n - 1 gaps, the count is the largest n for which n times the minimum plus n - 1 gaps fits, and at least one. A column count changes the wall’s height and not its width, so that number is stable too.

Reading order is down each column, not across. The third card is not necessarily beside the second. Where order matters, a form or a ranked list, the answer is a column.

A masonry never virtualizes. Which column an item is in depends on every item before it, so there is no arithmetic from an index to a position. A long wall is affordable because a scroll does not paint what is off screen, which is in Scroll.

Attributes

AttributeTypeDefaultWhat it does
columnsnumberunsetA fixed count. Two columns at 1200 are two columns at 720, half as wide and twice as tall
min-column-widthnumber320 when neither is writtenAs many columns as fit at this width, at least one
idstringnoneThe node’s id, which lands on the masonry node
classstringnoneClasses separated by spaces

The two are exclusive. A masonry given both is an IllegalArgumentException at construction, and so is columns=0. Neither is clamped, because a layout that silently disagrees with the document is found by looking at a picture and a document that throws is found by running anything.

Important

Put a wall where something gives it a width. A masonry-column is flex-basis: 0, so a masonry in a box that sizes to its content comes out zero wide with its cards hanging out of it, in either mode.

The showcase’s walls

Every screen in the showcase is a masonry, because a card is as tall as its contents and no two cards are the same height. Laid out in equal rows the short ones would sit in acres of empty surface. The Basic screen says min-column-width=560, so it is two columns at 1200 and one readable column at 720. The Panels, Overlays and Forms screens keep a count, columns=2 and columns=3, and a screen written in Java hands Masonry.UNSET for the mode it did not choose.

Styling

NodeWhat it isIn controls.css
masonryThe wallflex-direction: row; align-items: flex-start; gap: 12px
masonry-columnOne columnflex-direction: column; gap: 12px; flex-basis: 0; flex-grow: 1
masonry-cellOne card’s box, which exists to report a heightflex-direction: column

The gap on both axes is the stylesheet’s: the wall’s gap is between columns and the column’s gap is between cards. The responsive count reads the wall’s resolved gap. There are no pseudo-classes and no variant classes of its own. The showcase’s .wall adds flex-shrink: 0.

#wall { gap: 16px; }
#wall masonry-column { gap: 16px; }

Read more