Navigation

v0.0.4
Layouts

Bento

Tiles of a few sizes fitted into one box, in columns that follow its room.

On this page

A bento fits a few tiles of different sizes into one box, the way a product's features or a profile's highlights are shown: one large, a tall one beside it, a few small ones around them. It is a layout: it draws no surface, and the tiles are cards, sheets and whatever they hold.

What it knows is how many columns there are and how far a tile may span. Both follow the room the bento is given, not the viewport: four columns from 48rem, two from 28rem, one below. A tile that spans two columns in a wide bento is the whole row in two columns and one cell in one, so the same markup is a mosaic on a desktop and a plain column on a phone.

Bento

<section class="flex flex-col gap-xl p-xl bg-surface-base fg-text-body text-sm" aria-labelledby="bento-title">  <header class="flex flex-col gap-xs max-w(560)">    <p class="eyebrow">Platform</p>    <h2 class="heading heading-xl" id="bento-title">Everything a deploy needs</h2>  </header>  <div class="bento">    <article class="card card-split bento-large">      <img class="card-media" src="/demo/moonstone.svg" alt>      <header class="card-header">        <h3 class="card-title">Live at the edge</h3>        <p class="card-description">Every push is served from 14 regions before the review is done.</p>      </header>      <p class="card-body">Static files, functions and images are cached where the reader is, and purged the moment a deploy replaces them.</p>      <footer class="card-footer">        <a class="btn btn-sm" href="#regions">See the regions</a>      </footer>    </article>    <article class="card bento-tall">      <header class="card-header">        <h3 class="card-title">Reviewed together</h3>        <p class="card-description">Comments land on the preview, beside what they are about.</p>      </header>      <div class="card-body flex flex-col gap-sm">        <div class="avatar-group" role="group" aria-label="Ada Lovelace, Alan Turing, Grace Hopper and 5 others">          <span class="avatar avatar-sm">            <img class="avatar-image" src="/demo/portrait-1.svg" alt>          </span>          <span class="avatar avatar-sm">            <img class="avatar-image" src="/demo/portrait-2.svg" alt>          </span>          <span class="avatar avatar-sm">            <img class="avatar-image" src="/demo/portrait-3.svg" alt>          </span>          <span class="avatar avatar-sm" aria-hidden="true">+5</span>        </div>        <p class="m(0)">Eight people left 23 comments on this week's previews. Four are still open.</p>      </div>      <footer class="card-footer">        <a class="btn btn-sm" href="#comments">Open comments</a>      </footer>    </article>    <article class="card">      <div class="stat">        <p class="stat-label">Cold builds</p>        <div class="stat-row">          <p class="stat-value">38s</p>          <p class="stat-delta">21% faster</p>        </div>        <p class="stat-description">Caches are restored before the install.</p>      </div>    </article>    <article class="card">      <header class="card-header">        <h3 class="card-title">Rollbacks</h3>        <p class="card-description">Any deploy is one click away, and so is the one before it.</p>      </header>    </article>    <article class="card bento-wide">      <header class="card-header">        <h3 class="card-title">Builds this week</h3>        <p class="card-description">Thursday so far</p>      </header>      <ol class="chart chart-quiet" style="--chart-height: 4.5rem" aria-label="Builds per day, this week">        <li class="chart-item" style="--value: 42">          <span class="chart-label">Mon</span>          <span class="chart-value">42</span>        </li>        <li class="chart-item" style="--value: 58">          <span class="chart-label">Tue</span>          <span class="chart-value">58</span>        </li>        <li class="chart-item" style="--value: 36">          <span class="chart-label">Wed</span>          <span class="chart-value">36</span>        </li>        <li class="chart-item" style="--value: 71" aria-current="true">          <span class="chart-label">Thu</span>          <span class="chart-value">71</span>        </li>        <li class="chart-item" style="--value: 0">          <span class="chart-label">Fri</span>          <span class="chart-value">0</span>        </li>        <li class="chart-item" style="--value: 0">          <span class="chart-label">Sat</span>          <span class="chart-value">0</span>        </li>        <li class="chart-item" style="--value: 0">          <span class="chart-label">Sun</span>          <span class="chart-value">0</span>        </li>      </ol>    </article>    <article class="sheet sheet-inverse bento-wide flex flex-col justify-between gap-md p(20)">      <div class="flex flex-col gap-3xs">        <h3 class="heading heading-sm fg-text-inverse">Start on the free plan</h3>        <p class="m(0)">Three projects and every feature above. No card needed.</p>      </div>      <div class="flex">        <a class="btn btn-sm" href="#start">Create a project</a>      </div>    </article>  </div></section>
Choose Tablet, then Phone: four columns become two, then one, and each tile spans only as far as there are columns.

#How it is built

  • Columns counted from its own width. A container cannot query its own size, so the bento counts its columns the way an auto grid does: repeat(auto-fill, …) with a narrowest track. That narrowest track is a quarter of the width from 48rem, a half from 28rem and the whole below, so auto-fill makes four columns, two or one, and never three.
  • Spans that follow the columns. The bento is a size container named bento, measured at the same width. bento-wide, bento-tall and bento-large span only from 28rem of bento, where there are two columns to span. bento-full spans the row at every width.
  • Rows with a floor. Rows are at least --bento-row tall, 10rem, and grow with what they hold. A tall tile is two rows and the gap between them, whatever is in it.
  • Tiles in the order of the markup. The six tiles above are written so they pack in four columns and in two: the large one and the tall one first, the two small ones that fill the column beside the tall one, then the two wide ones.
  • The tiles are components. A split card puts the picture beside its words while the tile is wide and above them when it is not. The figures are a stat and a quiet chart; the last tile is the page's one inverse sheet.

#Tiles

A tile with no class takes one cell. Four parts make a tile larger:

ClassIn four columnsIn twoIn one
bento-wideTwo columnsThe rowOne cell
bento-tallTwo rowsTwo rowsOne cell
bento-largeTwo columns, two rowsThe row, two rowsOne cell
bento-fullThe rowThe rowThe row

Tiles

<div class="bento p-lg bg-surface-base fg-text-muted text-2xl font-mono" style="--bento-row: 5rem">  <div class="well flex items-center justify-center bento-large">bento-large</div>  <div class="well flex items-center justify-center bento-tall">bento-tall</div>  <div class="well flex items-center justify-center">One cell</div>  <div class="well flex items-center justify-center">One cell</div>  <div class="well flex items-center justify-center bento-wide">bento-wide</div>  <div class="well flex items-center justify-center">One cell</div>  <div class="well flex items-center justify-center">One cell</div>  <div class="well flex items-center justify-center bento-full">bento-full</div></div>
Choose Tablet, then Phone: each span holds while there are columns to span, and lets go when there are not.

Keep spans to a few tiles. A bento where every tile is large is an auto grid with larger tiles, and one where nothing is large has nothing to say first.

#Order

Tiles are placed in the order of the markup, each in the first place it fits after the one before it. When a wide tile does not fit at the end of a row, it starts the next one and leaves a hole behind it. The grid does not move a later tile back into that hole, because then the order seen would not be the order read: a keyboard and a screen reader follow the markup.

So write the tiles in an order that packs. Where the order means nothing, as in a wall of logos or a set of pictures, bento-dense lets a later tile move up into a hole.

Order

<div class="flex flex-col gap-md w-full fg-text-muted text-sm">  <p class="eyebrow">bento</p>  <div class="bento" style="--bento-row: 3rem">    <div class="well flex items-center justify-center">1</div>    <div class="well flex items-center justify-center bento-wide">2</div>    <div class="well flex items-center justify-center bento-wide">3</div>    <div class="well flex items-center justify-center">4</div>    <div class="well flex items-center justify-center">5</div>  </div>  <p class="eyebrow">bento bento-dense</p>  <div class="bento bento-dense" style="--bento-row: 3rem">    <div class="well flex items-center justify-center">1</div>    <div class="well flex items-center justify-center bento-wide">2</div>    <div class="well flex items-center justify-center bento-wide">3</div>    <div class="well flex items-center justify-center">4</div>    <div class="well flex items-center justify-center">5</div>  </div></div>
In a tray narrower than 28rem of bento every tile is one cell and no hole is left; widen the window to see one.

In the first bento the wide tile leaves a hole after the first. In the second, the fourth tile has moved up into it: seen, it is second; read, it is still fourth.

#Rows

--bento-row is the least a row may be, 10rem by default. It is a floor, not a height: a tile with more words makes its row taller, and the tiles beside it stretch to match, so a row is never cut. Lower it for tiles that hold a figure or a line, such as the diagrams on this page at 5rem; raise it for tiles that hold a picture.

--bento-gap is the space between tiles, 1rem. It is a property and not a gap utility because the columns are counted with it.

#When to use it

The tilesUse
Of a few sizes, some meaning more than othersA bento
Of one size, as many as there areAn auto grid
Each as tall as what it holds, as in a galleryMasonry
In a number of columns the design fixesgrid grid-cols(3) and grid-col-span(2), with a container variant

A bento is for a set that is composed: six to ten tiles, arranged once. A collection that grows, such as projects or search results, has no tile that matters more than the rest and belongs in an auto grid.

#Accessibility

  • The order of the markup is the reading order and the order of the keyboard. Write the tile that matters most first, and keep bento-dense for sets whose order means nothing.
  • Size is emphasis a screen reader does not hear. Give the large tile the heading the reader most needs, and let each tile's heading say what it is about, so the list of headings reads as the set.
  • A tile that is one link is a card with card-interactive on the link itself, not a card with a link stretched over it.

#Classes

ClassDescription
bentoThe grid: four columns from 48rem of its own width, two from 28rem, one below, and rows at least --bento-row tall. A size container named bento. It draws nothing
bento-denseA later tile moves up into a hole an earlier span left. The order seen is no longer the order read, so use it only for a set whose order means nothing. With bento
bento-wideA tile two columns wide, from 28rem of bento; the whole row in two columns
bento-tallA tile two rows tall, from 28rem of bento
bento-largeA tile two columns wide and two rows tall, from 28rem of bento
bento-fullA tile across the whole row, at every width

#Custom properties

PropertyDefaultDescription
--bento-row10remThe least a row may be; a row grows with the tallest thing in it
--bento-gap1remThe space between tiles, which the columns are counted with

#Source

@master/ui/layouts/bento.css
/* * Bento: a few tiles of different sizes fitted into one box, the way a product's features or a profile's * highlights are shown. It is a layout: it draws no surface; the tiles are cards, sheets or figures. * * What it knows is how many columns there are and how far a tile may span. Both follow the room the bento is * given, never the viewport: four columns from 48rem, two from 28rem, one below. The bento is a size container, * named bento, so the tiles read the same width the columns are counted from. The columns are counted without * a query on the bento itself (a container cannot query its own size): the narrowest a track may be jumps from a * quarter to a half to the whole of the width at those two thresholds, and auto-fill makes as many tracks as * that allows. A pixel is taken off each, so rounding never costs a column. * * A tile takes one cell. bento-wide spans two columns, bento-tall two rows, bento-large both, bento-full the * row. A span never asks for more columns than there are: in two columns a wide tile is the row, and in one * column every tile is one cell. Rows are at least --bento-row tall and grow with what they hold, so a tile * never cuts its words. * * Tiles are placed in the order of the markup, which is also the order a keyboard and a screen reader follow. * The grid does not move a later tile up into a hole: arrange the tiles so they pack, or add bento-dense to a * set whose order means nothing. */@layer components {  .bento {    --bento-row: 10rem;    --bento-gap: 1rem;    display: grid;    grid-template-columns: repeat(auto-fill, minmax(calc(clamp(clamp((100% - var(--bento-gap) * 3) / 4, (48rem - 100%) * 999, (100% - var(--bento-gap)) / 2), (28rem - 100%) * 999, 100%) - 1px), 1fr));    grid-auto-rows: minmax(var(--bento-row), auto);    gap: var(--bento-gap);    min-width: 0;    margin: 0;    padding: 0;    list-style: none;    container: bento / inline-size;    &.bento-dense {      grid-auto-flow: row dense;    }  }  .bento-full {    grid-column: 1 / -1;  }  @container bento (inline-size >= 28rem) {    .bento-wide,    .bento-large {      grid-column: span 2;    }    .bento-tall,    .bento-large {      grid-row: span 2;    }  }}