Navigation

v0.0.4
Layouts

Masonry

Tiles of their own heights, packed into lanes with no gaps under them.

On this page

Masonry packs tiles of their own heights into lanes of one width. Each tile goes where the lanes end highest, so a short picture leaves no row of empty space beside a tall one, and every picture keeps the shape it was taken in. It is a layout: it draws no surface; the tiles are figures, cards and sheets.

Like an auto grid, it counts its own lanes from the room it is given, none narrower than --masonry-min, and needs no query. Where the browser has grid lanes, the grid does the packing; elsewhere a multi-column box does the same with one difference, in the order the tiles are read, described below.

Masonry

<section class="p-lg bg-surface-base fg-text-body text-sm" aria-labelledby="masonry-title">  <div class="flex flex-col gap-xl max-w(1120) mx-auto">    <header class="flex flex-col gap-xs max-w(560)">      <p class="eyebrow">Field notes</p>      <h2 class="heading heading-xl" id="masonry-title">A week on the coast</h2>      <p class="lead lead-sm">Fourteen pictures, each at the shape it was taken in.</p>    </header>    <ul class="masonry" style="--masonry-min: 10rem">      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/dunes.svg" alt="Dunes in the afternoon, their lee sides in shadow" style="--figure-ratio: 3 / 4">          <figcaption class="figure-caption">Monday, the dunes</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/tide.svg" alt="A pale sun low over flat water" style="--figure-ratio: 4 / 3">          <figcaption class="figure-caption">Low tide at seven</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/cairn.svg" alt="Five stones balanced on a beach" style="--figure-ratio: 4 / 5">          <figcaption class="figure-caption">A cairn on the point</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/harbor.svg" alt="A wooden jetty running out over calm water" style="--figure-ratio: 3 / 2">          <figcaption class="figure-caption">The old jetty</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/pines.svg" alt="Rows of pines fading into fog" style="--figure-ratio: 2 / 3">          <figcaption class="figure-caption">Fog in the pines</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/steps.svg" alt="Concrete steps half in shadow" style="--figure-ratio: 1 / 1">          <figcaption class="figure-caption">Stairs to the harbour</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/lighthouse.svg" alt="A lighthouse on a dark headland, its beam over the sea" style="--figure-ratio: 3 / 4">          <figcaption class="figure-caption">The light at dusk</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/moonstone.svg" alt="A full moon above two ridges" style="--figure-ratio: 16 / 10">          <figcaption class="figure-caption">Moonrise over the ridge</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/tide.svg" alt="The sea at slack tide" style="--figure-ratio: 1 / 1">          <figcaption class="figure-caption">Wednesday, still water</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/dunes.svg" alt="Dune ridges reshaped by the wind" style="--figure-ratio: 4 / 3">          <figcaption class="figure-caption">Ridges after the wind</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/harbor.svg" alt="Jetty posts standing in the water" style="--figure-ratio: 4 / 5">          <figcaption class="figure-caption">Posts at high water</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/cairn.svg" alt="A small stack of beach stones" style="--figure-ratio: 1 / 1">          <figcaption class="figure-caption">Stones by the door</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/pines.svg" alt="A line of pines in mist" style="--figure-ratio: 4 / 3">          <figcaption class="figure-caption">The road through the wood</figcaption>        </figure>      </li>      <li class="masonry-item">        <figure class="figure">          <img class="figure-media" src="/demo/steps.svg" alt="Steps in early light" style="--figure-ratio: 3 / 4">          <figcaption class="figure-caption">Morning on the steps</figcaption>        </figure>      </li>    </ul>  </div></section>
Choose Tablet, then Phone: six lanes become four, then two, and no picture is cropped to line up with another.

#How it is built

  • Lanes counted from the room. masonry makes as many equal lanes as fit at --masonry-min or wider, and never one wider than itself, so a phone does not overflow. The gallery sets --masonry-min to 10rem, which keeps two lanes on a phone.
  • Packing by the platform. Where display: grid-lanes is supported, the lanes are grid columns and the browser places each tile in the lane that ends highest. Elsewhere they are the columns of a multi-column box, which fills one column before the next and balances their heights.
  • Each tile whole. masonry-item keeps a tile from breaking across two columns, and keeps the gap above it. The fallback needs a class on the tile for both, so every tile has one.
  • Tiles at their own shape. Each picture is a figure with --figure-ratio set to its shape, so the lane holds its place before the picture loads and nothing moves when it arrives.

#Lanes

--masonry-min is the narrowest a lane may be, 14rem by default. A smaller minimum makes more, narrower lanes: 9 or 10rem for pictures, which keeps two lanes on a phone; 16rem or more for cards with paragraphs. --masonry-gap is the space between lanes and between the tiles in a lane, 1rem.

Tiles need not be pictures. Anything that is as tall as what it holds can share the lanes: a note, a quote, a figure with a longer caption.

Mixed tiles

<ul class="masonry w-full" style="--masonry-min: 11rem">  <li class="masonry-item">    <article class="card">      <header class="card-header">        <h3 class="card-title">Tuesday</h3>        <p class="card-description">Low tide at seven</p>      </header>      <p class="card-body">Walked out past the last post of the jetty. The sand was ribbed like a roof and full of small shells.</p>    </article>  </li>  <li class="masonry-item">    <figure class="figure">      <img class="figure-media" src="/demo/lighthouse.svg" alt="A lighthouse on a dark headland" style="--figure-ratio: 4 / 5">      <figcaption class="figure-caption">The light comes on at twenty past five.</figcaption>    </figure>  </li>  <li class="masonry-item">    <div class="sheet p-lg">      <blockquote class="quote">        <p class="quote-text">Nothing here is in a hurry, least of all the sea.</p>        <footer class="quote-source">          <span class="quote-name">The harbour master</span>        </footer>      </blockquote>    </div>  </li>  <li class="masonry-item">    <article class="card">      <header class="card-header">        <h3 class="card-title">To bring back</h3>      </header>      <ul class="card-body bullets m(0)">        <li class="bullets-item">A stone for the desk</li>        <li class="bullets-item">The tide table</li>        <li class="bullets-item">Film for the camera</li>      </ul>    </article>  </li>  <li class="masonry-item">    <figure class="figure">      <img class="figure-media" src="/demo/cairn.svg" alt="Five stones balanced on a beach" style="--figure-ratio: 1 / 1">      <figcaption class="figure-caption">Someone builds it again every morning.</figcaption>    </figure>  </li></ul>

#Grid lanes and the fallback

Masonry is written once and laid out by whichever the browser has.

With grid lanesWithout them
The lanesThe columns of display: grid-lanesThe columns of a multi-column box, columns: var(--masonry-min)
Where a tile goesThe lane that ends highest, in the order of the markupDown the first column until it is full, then the next
The order seenAcross the lanes: the first tiles make the top rowDown each column: the first tiles make the first column
The gap between tilesThe grid's gapA margin, which the top of a column cuts off

Grid lanes are new, and not every engine has them yet; browser support lists the layouts that use them. Without them nothing is lost but the order: every tile is packed, whole and at its shape. Follow the numbers below to see which one your browser used.

Order

<ol class="masonry w-full fg-text-muted text-sm" style="--masonry-min: 8rem">  <li class="masonry-item well flex items-center justify-center h(96)">1</li>  <li class="masonry-item well flex items-center justify-center h(160)">2</li>  <li class="masonry-item well flex items-center justify-center h(64)">3</li>  <li class="masonry-item well flex items-center justify-center h(128)">4</li>  <li class="masonry-item well flex items-center justify-center h(112)">5</li>  <li class="masonry-item well flex items-center justify-center h(80)">6</li>  <li class="masonry-item well flex items-center justify-center h(144)">7</li>  <li class="masonry-item well flex items-center justify-center h(72)">8</li>  <li class="masonry-item well flex items-center justify-center h(120)">9</li></ol>
Across the lanes with grid lanes, down each column without them.

Choose masonry when that difference does not matter: a gallery, a board of notes, a wall of quotes, read in any order. When the order does matter, as in a feed with the newest first, use an auto grid, whose rows always read across.

#When to use it

The tilesUse
Each as tall as what it holds, read in any orderMasonry
Of one size, or meant to line up in rowsAn auto grid
Of a few sizes, arranged once, some meaning moreA bento
On one row, scrolled throughA carousel

#Accessibility

  • The order of the markup is the reading order and the order of the keyboard in both layouts. With grid lanes the order seen follows it across the lanes; in the fallback it follows it down each column. Neither moves a tile out of its place in the sequence.
  • Use a list, <ul class="masonry">, for a set of like things, so their number is announced. The tiles are its items.
  • A picture that says something needs alt text even when it has a caption: the caption adds to the picture, and does not stand in for it.

#Classes

ClassDescription
masonryThe lanes: as many equal ones as fit at --masonry-min or wider, packed with grid lanes where the browser has them and as columns elsewhere. It draws nothing
masonry-itemA tile. It is never split across two lanes, and keeps --masonry-gap from the one above it

#Custom properties

PropertyDefaultDescription
--masonry-min14remThe narrowest a lane may be; a lane is never wider than the masonry itself
--masonry-gap1remThe space between lanes and between the tiles in a lane

#Source

@master/ui/layouts/masonry.css
/* * Masonry: tiles of their own heights, packed into as many equal lanes as fit, none narrower than * --masonry-min. Each tile goes where the lanes end highest, so no row of gaps is left under a short one. Like * the auto grid it counts its own lanes from the room it is given and needs no query. It draws no surface. * * Where the browser has grid lanes (display: grid-lanes), the lanes are a grid's columns: tiles are placed in the * order of the markup, each into the lane that ends highest, and the gap is the grid's. Elsewhere the lanes are * the columns of a multi-column box: the same packing, filled down the first column before the next. A tile * never breaks across two lanes, and the space between two tiles is a margin that a column's top cuts off. * * Every tile is a masonry-item, because the fallback has to keep each one whole and spaced. A set of like * things is a list, <ul class="masonry">, so the masonry clears a list's indent and markers. */@layer components {  .masonry {    --masonry-min: 14rem;    --masonry-gap: 1rem;    columns: var(--masonry-min);    column-gap: var(--masonry-gap);    min-width: 0;    margin: 0;    padding: 0;    list-style: none;    @supports (display: grid-lanes) {      display: grid-lanes;      grid-template-columns: repeat(auto-fill, minmax(min(100%, var(--masonry-min)), 1fr));      gap: var(--masonry-gap);    }  }  .masonry-item {    min-width: 0;    break-inside: avoid;    & + & {      margin-block-start: var(--masonry-gap);    }    @supports (display: grid-lanes) {      & + & {        margin-block-start: 0;      }    }  }}