A Bento dashboard and a masonry gallery can both contain rectangles of different sizes. They do not, however, use the same placement rules.
For Bento layouts, ordinary CSS Grid is usually enough. You decide which cards span which tracks and let the browser size those tracks. For true masonry, rows must stop lining up. Each item needs to stack into an available column or lane according to its height. grid-auto-flow: dense does not do that.
This distinction matters when choosing a CSS-only implementation. A layout that looks convincing with six static placeholders may behave differently with translated headings, taller images, keyboard navigation, or a narrow container.
Choose the placement algorithm before writing CSS
| Layout technique | What it actually does | Good fit | Main limitation |
|---|---|---|---|
| Grid with named areas | Positions cards on a shared row-and-column grid | Curated Bento dashboards | You maintain the arrangement and responsive states |
Grid with dense auto-placement | Backfills unoccupied grid cells | Decorative tile puzzles | Can change visual order; rows still share tracks |
| CSS multi-column | Flows content down one column, then into the next | Decorative galleries with varying tile heights | Column-first reading flow, not masonry's shortest-lane placement |
| Grid Lanes (limited browser support) | Stacks items in available lanes without shared masonry-axis row tracks | Progressive enhancement for true masonry | Not widely supported; needs a fallback |
The distinction between dense packing and genuine lane stacking follows the Grid auto-placement documentation and the Grid Lanes guide.
Build a Bento layout that responds to its container
Consider a project-summary section with a featured card, a metric, some notes, and two smaller cards. The cards have an editorial order: featured information first, then supporting details. Start with that same order in HTML.
<div class="showcase">
<section class="bento" aria-label="Project highlights">
<article class="bento__feature">
<h2>Featured project</h2>
<p>Explain the project's purpose and the main design decision.</p>
</article>
<article class="bento__metric">
<h2>Key result</h2>
<p>Put a verified result here, or describe an outcome without a number.</p>
</article>
<article class="bento__notes">
<h2>Implementation notes</h2>
<p>Describe a tradeoff that matters to the reader.</p>
</article>
<article class="bento__details">
<h2>Technical details</h2>
<p>Include the stack or architecture where useful.</p>
</article>
<article class="bento__extras">
<h2>Next steps</h2>
<p>Explain what still needs to be done.</p>
</article>
</section>
</div>
The text is sample copy, not a claim about a completed project. There is no need for five independent links unless the cards genuinely lead to different destinations.
Now define a single-column layout first. A container query activates the art-directed version only when the component's own available inline size is large enough.
.showcase {
container-type: inline-size;
}
.bento {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 1rem;
}
.bento > * {
min-width: 0;
padding: clamp(1rem, 2vw, 1.5rem);
border: 1px solid #b7bec9;
border-radius: 1rem;
overflow-wrap: anywhere;
}
@container (min-width: 46rem) {
.bento {
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-areas:
"feature feature metric metric"
"feature feature notes notes"
"details details extras extras";
}
.bento__feature { grid-area: feature; }
.bento__metric { grid-area: metric; }
.bento__notes { grid-area: notes; }
.bento__details { grid-area: details; }
.bento__extras { grid-area: extras; }
}
Three details make this more robust than a fixed screenshot recreation.
First, the markup follows a sensible order even without CSS. The named areas preserve that broad reading sequence on wider layouts; they do not move the key action to an unrelated corner.
Second, minmax(0, 1fr) permits the tracks to shrink below their default automatic minimum. min-width: 0 and overflow-wrap: anywhere also help prevent long unbroken content from forcing overflow. These are complementary protections, not a reason to skip narrow-width testing.
Third, there is no fixed height on the cards or rows. Their content can grow. That means the result may be less geometrically perfect than a mockup, but it avoids clipping text at higher zoom or with longer translations.
The breakpoint is deliberately attached to .showcase rather than the viewport. A 1000px-wide screen can still contain a 400px sidebar. For the mechanism behind this choice, see CSS Container Queries Guide.
When not to use named areas: If cards are inserted and removed frequently by users, a hardcoded five-area template becomes awkward. An auto-placed Grid with simple spans, or a different composition entirely, may be more maintainable. Named areas work best when the layout is intentionally curated.
Why grid-auto-flow: dense is not masonry
A standard Grid creates row tracks that are shared across columns. Making one tile taller or spanning extra rows does not turn those shared tracks into independent vertical stacks.
The following deliberately small example reveals a second problem: dense packing can change visual order.
<div class="puzzle">
<a class="wide" href="#tile-one">One</a>
<a class="wide" href="#tile-two">Two</a>
<a href="#tile-three">Three</a>
</div>
<div class="puzzle-details">
<p id="tile-one"><strong>One:</strong> First in source order and first visually.</p>
<p id="tile-two"><strong>Two:</strong> Second in source order, placed in row two.</p>
<p id="tile-three"><strong>Three:</strong> Third in source order, backfilled into row one.</p>
</div>
.puzzle {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-auto-rows: 6rem;
grid-auto-flow: row dense;
gap: 0.75rem;
}
.puzzle .wide {
grid-column: span 2;
}
The first wide link occupies columns 1 and 2 in the first row. The second wide link cannot fit in the remaining column, so it goes to the next row. Dense packing backfills the remaining first-row cell with Three.
Visually, the first row contains One, Three, followed by Two on the next row. But the DOM and default keyboard sequence are One, Two, Three. This is a genuine layout effect, not a rendering bug.
dense is useful for a decorative arrangement where position carries no reading meaning. It is a risky default for navigation, task cards, ranked results, or other interfaces where scanning and focus order should agree. The MDN accessibility guidance for Grid explains why visual placement does not automatically rewrite document reading order.
The newer reading-flow property can influence navigation order in some implementations, but it is still limited in browser availability. Do not use it as a universal accessibility patch for an unnecessarily reordered layout. A coherent DOM order and a simpler placement strategy are safer defaults. For more on meaningful structure, read Why Semantic HTML Matters.
Make a CSS-only gallery, with an explicit fallback
For an image-led gallery with different tile heights, two native techniques deserve consideration:
- Multi-column layout is widely usable today and can produce a waterfall-like silhouette, but its content flows down columns.
- Grid Lanes provides the lane-stacking behavior defined in CSS Grid Layout Level 3. Safari 26.4 ships support, but the feature is still not broadly available across major browser families, so a fallback remains important.
Here is a small runnable sample using varying card heights. The values stand in for real image or content dimensions; replace them with actual gallery items.
<ul class="gallery" aria-label="Layout samples">
<li style="--tile-height: 14rem">Portrait study</li>
<li style="--tile-height: 9rem">Landscape</li>
<li style="--tile-height: 18rem">Panorama</li>
<li style="--tile-height: 11rem">Detail</li>
<li style="--tile-height: 16rem">Textures</li>
<li style="--tile-height: 12rem">Patterns</li>
</ul>
Start with multi-column as the broad fallback. break-inside: avoid asks the fragmentation algorithm not to split a tile between columns.
.gallery {
list-style: none;
margin: 0;
padding: 0;
columns: 15rem;
column-gap: 1rem;
}
.gallery > li {
min-height: var(--tile-height, 9rem);
margin-block: 0 1rem;
padding: 1rem;
border-radius: 0.75rem;
background: #e2e8f0;
break-inside: avoid;
}
This is not the same as filling the shortest available lane, and it does not guarantee a particular visual item order across columns. Multi-column is a fragmentation layout. It flows the source into columns in sequence, commonly producing a column-first visual reading pattern. That may be acceptable for decorative or sequential content; it is often a poor match for a horizontally scanned set of interactive product cards.
The multi-column documentation and the break-inside reference describe the relevant behavior.
Enhance to Grid Lanes where available
Keep the same HTML and fallback CSS. Add this after the previous rules:
@supports (display: grid-lanes) {
.gallery {
columns: auto;
display: grid-lanes;
grid-template-columns:
repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
gap: 1rem;
}
.gallery > li {
margin-block: 0;
}
}
The @supports block only applies when the browser recognizes the proposed grid-lanes display value. In that mode the rows no longer behave like conventional shared Grid tracks; items stack in lanes. The min(100%, 15rem) minimum also prevents the intended 15rem column width from forcing a single column wider than a narrow container.
This syntax follows the current CSS Grid Lanes documentation and WebKit's explanation of the proposal. Older examples using grid-template-rows: masonry reflect earlier experiments. Do not mistake them for a widely deployable standard.
Compatibility boundary: An isolated Playwright reproduction of the multi-column fallback, Bento layout, and dense Grid packing was executed with Chromium 144.0.7559.96 on Linux. Chromium reported CSS.supports('display', 'grid-lanes') === false, so the Grid Lanes enhancement was not exercised in that browser. Safari 26.4 supports Grid Lanes, but its rendering behavior was not independently tested for this article. Check your target browser versions and test with real content before depending on lane placement.
What about an interactive gallery?
If each card contains a button, a link, or information that must be scanned left to right, begin with an ordinary source-ordered Grid instead. It may leave vertical space beneath shorter items, which is an acceptable tradeoff for predictable focus order.
.cards {
display: grid;
grid-template-columns:
repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
align-items: start;
gap: 1rem;
}
Notice the distinction: align-items: start keeps the cards from stretching to fill their row height, but it does not remove the shared row tracks or produce masonry. That remaining whitespace is expected.
Check the layout where screenshots tend to lie
Use the following checks before treating any of these patterns as production-ready:
- Shrink the component, not only the browser. Test a Bento section inside a narrow sidebar or split view. At widths below its container-query threshold, all five cards should flow in source order.
- Increase text length and zoom. Try long unbroken identifiers, 200% zoom, and translations. Look for clipping, horizontal scroll, and cards that overlap.
- Tab through interactive content. Compare visible progression with DOM order. On a dense puzzle, watch for focus jumping back to an earlier visual row.
- Remove CSS or disable the enhancement. The document should still communicate the correct priority. The gallery should remain usable without Grid Lanes.
- Use actual image dimensions. When replacing placeholder tiles with images, supply intrinsic width and height or a deliberate
aspect-ratio, appropriateobject-fit, and useful alt text. Do not claim a layout-shift improvement without measuring it.
These are observable acceptance checks, not a promise of improved Core Web Vitals. Native CSS can remove a layout library dependency when that library is no longer needed, but it does not automatically eliminate layout shifts, loading costs, or the need for JavaScript in interactive galleries.
The practical boundary
Use named-area Grid when you control the Bento composition. Use ordinary auto-placement when cards should retain natural source order. Use multi-column when a column-first gallery is acceptable. Consider Grid Lanes as a feature-detected enhancement while browser support remains limited. Reach for JavaScript only when the requirements actually demand behavior these CSS algorithms cannot provide, such as custom placement, drag-and-drop coordination, or virtualization.
The goal is not to eliminate JavaScript on principle. It is to choose a layout algorithm whose behavior you can explain, test, and maintain.
