30 CSS Grid Layouts24 / 30
Dense Packing
grid-auto-flow: dense, demonstrated honestly: numbered tiles with mixed spans, where the numbers reveal exactly which items the algorithm pulled backward to fill holes. The one grid feature that trades source order for zero gaps — and the demo teaches when that trade is safe.
Published Updated
The code
<section class="cgl-24" aria-label="Dense packing demo">
<p class="cgl-24__note">Numbers = DOM order. Spot the small tiles pulled backward to plug holes.</p>
<div class="cgl-24__grid">
<div class="cgl-24__cell" style="--s:2">1</div>
<div class="cgl-24__cell">2</div>
<div class="cgl-24__cell" style="--s:2;--r:2">3</div>
<div class="cgl-24__cell">4</div>
<div class="cgl-24__cell" style="--s:2">5</div>
<div class="cgl-24__cell">6</div>
<div class="cgl-24__cell">7</div>
<div class="cgl-24__cell" style="--s:2">8</div>
<div class="cgl-24__cell">9</div>
<div class="cgl-24__cell">10</div>
<div class="cgl-24__cell" style="--r:2">11</div>
<div class="cgl-24__cell">12</div>
<div class="cgl-24__cell">13</div>
<div class="cgl-24__cell" style="--s:2">14</div>
<div class="cgl-24__cell">15</div>
</div>
</section><section class="cgl-24" aria-label="Dense packing demo">
<p class="cgl-24__note">Numbers = DOM order. Spot the small tiles pulled backward to plug holes.</p>
<div class="cgl-24__grid">
<div class="cgl-24__cell" style="--s:2">1</div>
<div class="cgl-24__cell">2</div>
<div class="cgl-24__cell" style="--s:2;--r:2">3</div>
<div class="cgl-24__cell">4</div>
<div class="cgl-24__cell" style="--s:2">5</div>
<div class="cgl-24__cell">6</div>
<div class="cgl-24__cell">7</div>
<div class="cgl-24__cell" style="--s:2">8</div>
<div class="cgl-24__cell">9</div>
<div class="cgl-24__cell">10</div>
<div class="cgl-24__cell" style="--r:2">11</div>
<div class="cgl-24__cell">12</div>
<div class="cgl-24__cell">13</div>
<div class="cgl-24__cell" style="--s:2">14</div>
<div class="cgl-24__cell">15</div>
</div>
</section>.cgl-24,
.cgl-24 *,
.cgl-24 *::before,
.cgl-24 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-24 {
width: 100%;
min-height: 100vh;
--grape: oklch(0.55 0.18 310);
--bg: oklch(0.17 0.025 310);
font-family: ui-monospace,'Cascadia Code',Menlo,monospace;
background: var(--bg);
padding: 1.4rem;
}
.cgl-24 .cgl-24__note {
font-size: .72rem;
color: oklch(0.7 0.06 310);
margin-block-end: .9rem;
}
.cgl-24 .cgl-24__grid {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(min(76px,100%),1fr));
grid-auto-rows: 64px;
grid-auto-flow: row dense;
gap: 8px;
}
.cgl-24 .cgl-24__cell {
grid-column: span var(--s,1);
grid-row: span var(--r,1);
display: grid;
place-items: center;
font-size: 1.05rem;
font-weight: 700;
color: oklch(0.9 0.05 310);
background: color-mix(in oklab,var(--grape) 30%,var(--bg));
border: 1px solid color-mix(in oklab,var(--grape) 50%,var(--bg));
border-radius: 10px;
}
.cgl-24 .cgl-24__cell:nth-child(odd) {
background: color-mix(in oklab,var(--grape) 18%,var(--bg));
}.cgl-24,
.cgl-24 *,
.cgl-24 *::before,
.cgl-24 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-24 {
width: 100%;
min-height: 100vh;
--grape: oklch(0.55 0.18 310);
--bg: oklch(0.17 0.025 310);
font-family: ui-monospace,'Cascadia Code',Menlo,monospace;
background: var(--bg);
padding: 1.4rem;
}
.cgl-24 .cgl-24__note {
font-size: .72rem;
color: oklch(0.7 0.06 310);
margin-block-end: .9rem;
}
.cgl-24 .cgl-24__grid {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(min(76px,100%),1fr));
grid-auto-rows: 64px;
grid-auto-flow: row dense;
gap: 8px;
}
.cgl-24 .cgl-24__cell {
grid-column: span var(--s,1);
grid-row: span var(--r,1);
display: grid;
place-items: center;
font-size: 1.05rem;
font-weight: 700;
color: oklch(0.9 0.05 310);
background: color-mix(in oklab,var(--grape) 30%,var(--bg));
border: 1px solid color-mix(in oklab,var(--grape) 50%,var(--bg));
border-radius: 10px;
}
.cgl-24 .cgl-24__cell:nth-child(odd) {
background: color-mix(in oklab,var(--grape) 18%,var(--bg));
}/* No JavaScript — grid-auto-flow: row dense backfills holes; the numbered tiles expose the reordering. */
/* No JavaScript — grid-auto-flow: row dense backfills holes; the numbered tiles expose the reordering. */Here's a working CSS Grid Layout from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Dense Packing
Source: https://codefronts.com/layouts/css-grid-layouts/dense-packing/
grid-auto-flow: dense, demonstrated honestly: numbered tiles with mixed spans, where the numbers reveal exactly which items the algorithm pulled backward to fill holes. The one grid feature that trades source order for zero gaps — and the demo teaches when that trade is safe.
## HTML
```html
<section class="cgl-24" aria-label="Dense packing demo">
<p class="cgl-24__note">Numbers = DOM order. Spot the small tiles pulled backward to plug holes.</p>
<div class="cgl-24__grid">
<div class="cgl-24__cell" style="--s:2">1</div>
<div class="cgl-24__cell">2</div>
<div class="cgl-24__cell" style="--s:2;--r:2">3</div>
<div class="cgl-24__cell">4</div>
<div class="cgl-24__cell" style="--s:2">5</div>
<div class="cgl-24__cell">6</div>
<div class="cgl-24__cell">7</div>
<div class="cgl-24__cell" style="--s:2">8</div>
<div class="cgl-24__cell">9</div>
<div class="cgl-24__cell">10</div>
<div class="cgl-24__cell" style="--r:2">11</div>
<div class="cgl-24__cell">12</div>
<div class="cgl-24__cell">13</div>
<div class="cgl-24__cell" style="--s:2">14</div>
<div class="cgl-24__cell">15</div>
</div>
</section>
```
## CSS
```css
.cgl-24,
.cgl-24 *,
.cgl-24 *::before,
.cgl-24 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-24 {
width: 100%;
min-height: 100vh;
--grape: oklch(0.55 0.18 310);
--bg: oklch(0.17 0.025 310);
font-family: ui-monospace,'Cascadia Code',Menlo,monospace;
background: var(--bg);
padding: 1.4rem;
}
.cgl-24 .cgl-24__note {
font-size: .72rem;
color: oklch(0.7 0.06 310);
margin-block-end: .9rem;
}
.cgl-24 .cgl-24__grid {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(min(76px,100%),1fr));
grid-auto-rows: 64px;
grid-auto-flow: row dense;
gap: 8px;
}
.cgl-24 .cgl-24__cell {
grid-column: span var(--s,1);
grid-row: span var(--r,1);
display: grid;
place-items: center;
font-size: 1.05rem;
font-weight: 700;
color: oklch(0.9 0.05 310);
background: color-mix(in oklab,var(--grape) 30%,var(--bg));
border: 1px solid color-mix(in oklab,var(--grape) 50%,var(--bg));
border-radius: 10px;
}
.cgl-24 .cgl-24__cell:nth-child(odd) {
background: color-mix(in oklab,var(--grape) 18%,var(--bg));
}
```
## JavaScript
```js
/* No JavaScript — grid-auto-flow: row dense backfills holes; the numbered tiles expose the reordering. */
```Here's a working CSS Grid Layout from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Dense Packing
Source: https://codefronts.com/layouts/css-grid-layouts/dense-packing/
grid-auto-flow: dense, demonstrated honestly: numbered tiles with mixed spans, where the numbers reveal exactly which items the algorithm pulled backward to fill holes. The one grid feature that trades source order for zero gaps — and the demo teaches when that trade is safe.
## HTML
```html
<section class="cgl-24" aria-label="Dense packing demo">
<p class="cgl-24__note">Numbers = DOM order. Spot the small tiles pulled backward to plug holes.</p>
<div class="cgl-24__grid">
<div class="cgl-24__cell" style="--s:2">1</div>
<div class="cgl-24__cell">2</div>
<div class="cgl-24__cell" style="--s:2;--r:2">3</div>
<div class="cgl-24__cell">4</div>
<div class="cgl-24__cell" style="--s:2">5</div>
<div class="cgl-24__cell">6</div>
<div class="cgl-24__cell">7</div>
<div class="cgl-24__cell" style="--s:2">8</div>
<div class="cgl-24__cell">9</div>
<div class="cgl-24__cell">10</div>
<div class="cgl-24__cell" style="--r:2">11</div>
<div class="cgl-24__cell">12</div>
<div class="cgl-24__cell">13</div>
<div class="cgl-24__cell" style="--s:2">14</div>
<div class="cgl-24__cell">15</div>
</div>
</section>
```
## CSS
```css
.cgl-24,
.cgl-24 *,
.cgl-24 *::before,
.cgl-24 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-24 {
width: 100%;
min-height: 100vh;
--grape: oklch(0.55 0.18 310);
--bg: oklch(0.17 0.025 310);
font-family: ui-monospace,'Cascadia Code',Menlo,monospace;
background: var(--bg);
padding: 1.4rem;
}
.cgl-24 .cgl-24__note {
font-size: .72rem;
color: oklch(0.7 0.06 310);
margin-block-end: .9rem;
}
.cgl-24 .cgl-24__grid {
display: grid;
grid-template-columns: repeat(auto-fit,minmax(min(76px,100%),1fr));
grid-auto-rows: 64px;
grid-auto-flow: row dense;
gap: 8px;
}
.cgl-24 .cgl-24__cell {
grid-column: span var(--s,1);
grid-row: span var(--r,1);
display: grid;
place-items: center;
font-size: 1.05rem;
font-weight: 700;
color: oklch(0.9 0.05 310);
background: color-mix(in oklab,var(--grape) 30%,var(--bg));
border: 1px solid color-mix(in oklab,var(--grape) 50%,var(--bg));
border-radius: 10px;
}
.cgl-24 .cgl-24__cell:nth-child(odd) {
background: color-mix(in oklab,var(--grape) 18%,var(--bg));
}
```
## JavaScript
```js
/* No JavaScript — grid-auto-flow: row dense backfills holes; the numbered tiles expose the reordering. */
```How this works
With default row flow, a span-2 tile that doesn't fit the current row's remainder wraps — leaving a hole. grid-auto-flow: row dense lets the placement algorithm backfill: later SMALL items jump backward into earlier holes. The numbered tiles make the reordering visible — find tile 7 sitting before tile 5 and you've seen the algorithm work.
The demo's rule of thumb, encoded in its caption: dense is for BROWSING surfaces (galleries, tag clouds, moodboards) where visual completeness beats sequence. It's wrong for ranked or chronological content — and note that keyboard tab order still follows the DOM, so interactive dense grids create focus order that hops around the page (a WCAG 2.4.3 concern).
Make it yours
- Toggle dense off (grid-auto-flow: row) to compare — the demo's holes reappear instantly.
- Dense + auto-fit + minmax composes; the backfill works at every column count.
- Constrain chaos: give only 1×1 items freedom to backfill by keeping all spans ≤2.
- For ranked content needing gap-free looks, redesign spans (demo 23's arithmetic) instead of reordering.
Gotchas — read before shipping
- Never use dense on content where reading order carries meaning — search results, feeds, steps.
- Keyboard focus follows DOM order, not visual order; avoid dense for grids of links/buttons unless order genuinely doesn't matter.
- Dense can't fix holes caused by spans WIDER than remaining tracks at the grid's end — those need span discipline.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.2+ | 113+ | 111+ |
Floor set by oklch(), color-mix() as this demo is written. The core technique itself goes back further — Chrome 57+.
dense shipped with original grid — universal since 2017. The lesson, not the support, is the constraint.