30 CSS Grid Layouts12 / 30
Quantity Query
A grid that restyles itself based on HOW MANY children it has — pure CSS, via :has() and child-counting selectors. Three tags render as roomy feature cards; six or more automatically compact into a dense chip wall. The CMS-proof layout: editors add items, the design responds, nobody files a ticket.
Published Updated
The code
<section class="cgl-12" aria-label="Quantity query demo">
<p class="cgl-12__label">3 items → feature cards</p>
<ul class="cgl-12__grid">
<li><strong>Design</strong><span>Roomy, confident, editorial.</span></li>
<li><strong>Engineering</strong><span>Same class, same stylesheet.</span></li>
<li><strong>Research</strong><span>No modifiers, no JS.</span></li>
</ul>
<p class="cgl-12__label">8 items → the grid self-compacts via :has(> :nth-child(5))</p>
<ul class="cgl-12__grid">
<li><strong>Design</strong></li><li><strong>Engineering</strong></li><li><strong>Research</strong></li><li><strong>Ops</strong></li>
<li><strong>Data</strong></li><li><strong>Support</strong></li><li><strong>Legal</strong></li><li><strong>Growth</strong></li>
</ul>
</section><section class="cgl-12" aria-label="Quantity query demo">
<p class="cgl-12__label">3 items → feature cards</p>
<ul class="cgl-12__grid">
<li><strong>Design</strong><span>Roomy, confident, editorial.</span></li>
<li><strong>Engineering</strong><span>Same class, same stylesheet.</span></li>
<li><strong>Research</strong><span>No modifiers, no JS.</span></li>
</ul>
<p class="cgl-12__label">8 items → the grid self-compacts via :has(> :nth-child(5))</p>
<ul class="cgl-12__grid">
<li><strong>Design</strong></li><li><strong>Engineering</strong></li><li><strong>Research</strong></li><li><strong>Ops</strong></li>
<li><strong>Data</strong></li><li><strong>Support</strong></li><li><strong>Legal</strong></li><li><strong>Growth</strong></li>
</ul>
</section>.cgl-12,
.cgl-12 *,
.cgl-12 *::before,
.cgl-12 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-12 {
width: 100%;
min-height: 100vh;
--amber: oklch(0.78 0.15 75);
--coal: oklch(0.2 0.01 75);
--card: oklch(0.25 0.015 75);
font-family: system-ui,sans-serif;
background: var(--coal);
padding: 1.4rem;
display: flex;
flex-direction: column;
gap: .8rem;
}
.cgl-12 .cgl-12__label {
font-family: ui-monospace,Menlo,monospace;
font-size: .68rem;
color: color-mix(in oklab,var(--amber) 70%,white);
letter-spacing: .08em;
}
.cgl-12 .cgl-12__grid {
--floor: 200px;
--pad: 1.1rem;
--fs: .95rem;
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fit,minmax(min(var(--floor),100%),1fr));
gap: 12px;
margin-block-end: .6rem;
}
.cgl-12 .cgl-12__grid:has(>:nth-child(5)) {
--floor: 104px;
--pad: .6rem;
--fs: .78rem;
gap: 8px;
}
.cgl-12 .cgl-12__grid li {
background: var(--card);
border: 1px solid color-mix(in oklab,var(--amber) 25%,var(--card));
border-radius: 12px;
padding: var(--pad);
display: flex;
flex-direction: column;
gap: .4rem;
}
.cgl-12 .cgl-12__grid li strong {
font-size: var(--fs);
color: var(--amber);
}
.cgl-12 .cgl-12__grid li span {
font-size: .78rem;
line-height: 1.5;
color: oklch(0.75 0.02 75);
}
.cgl-12 .cgl-12__grid:has(>:nth-child(5)) li {
align-items: center;
text-align: center;
justify-content: center;
}
/* Legacy (pre-:has) equivalent: li:first-child:nth-last-child(n+5), li:first-child:nth-last-child(n+5) ~ li { ... } */.cgl-12,
.cgl-12 *,
.cgl-12 *::before,
.cgl-12 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-12 {
width: 100%;
min-height: 100vh;
--amber: oklch(0.78 0.15 75);
--coal: oklch(0.2 0.01 75);
--card: oklch(0.25 0.015 75);
font-family: system-ui,sans-serif;
background: var(--coal);
padding: 1.4rem;
display: flex;
flex-direction: column;
gap: .8rem;
}
.cgl-12 .cgl-12__label {
font-family: ui-monospace,Menlo,monospace;
font-size: .68rem;
color: color-mix(in oklab,var(--amber) 70%,white);
letter-spacing: .08em;
}
.cgl-12 .cgl-12__grid {
--floor: 200px;
--pad: 1.1rem;
--fs: .95rem;
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fit,minmax(min(var(--floor),100%),1fr));
gap: 12px;
margin-block-end: .6rem;
}
.cgl-12 .cgl-12__grid:has(>:nth-child(5)) {
--floor: 104px;
--pad: .6rem;
--fs: .78rem;
gap: 8px;
}
.cgl-12 .cgl-12__grid li {
background: var(--card);
border: 1px solid color-mix(in oklab,var(--amber) 25%,var(--card));
border-radius: 12px;
padding: var(--pad);
display: flex;
flex-direction: column;
gap: .4rem;
}
.cgl-12 .cgl-12__grid li strong {
font-size: var(--fs);
color: var(--amber);
}
.cgl-12 .cgl-12__grid li span {
font-size: .78rem;
line-height: 1.5;
color: oklch(0.75 0.02 75);
}
.cgl-12 .cgl-12__grid:has(>:nth-child(5)) li {
align-items: center;
text-align: center;
justify-content: center;
}
/* Legacy (pre-:has) equivalent: li:first-child:nth-last-child(n+5), li:first-child:nth-last-child(n+5) ~ li { ... } *//* No JavaScript — .grid:has(> :nth-child(5)) detects the item count and flips custom properties for the compact state. */
/* No JavaScript — .grid:has(> :nth-child(5)) detects the item count and flips custom properties for the compact state. */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: Quantity Query
Source: https://codefronts.com/layouts/css-grid-layouts/quantity-query/
A grid that restyles itself based on HOW MANY children it has — pure CSS, via :has() and child-counting selectors. Three tags render as roomy feature cards; six or more automatically compact into a dense chip wall. The CMS-proof layout: editors add items, the design responds, nobody files a ticket.
## HTML
```html
<section class="cgl-12" aria-label="Quantity query demo">
<p class="cgl-12__label">3 items → feature cards</p>
<ul class="cgl-12__grid">
<li><strong>Design</strong><span>Roomy, confident, editorial.</span></li>
<li><strong>Engineering</strong><span>Same class, same stylesheet.</span></li>
<li><strong>Research</strong><span>No modifiers, no JS.</span></li>
</ul>
<p class="cgl-12__label">8 items → the grid self-compacts via :has(> :nth-child(5))</p>
<ul class="cgl-12__grid">
<li><strong>Design</strong></li><li><strong>Engineering</strong></li><li><strong>Research</strong></li><li><strong>Ops</strong></li>
<li><strong>Data</strong></li><li><strong>Support</strong></li><li><strong>Legal</strong></li><li><strong>Growth</strong></li>
</ul>
</section>
```
## CSS
```css
.cgl-12,
.cgl-12 *,
.cgl-12 *::before,
.cgl-12 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-12 {
width: 100%;
min-height: 100vh;
--amber: oklch(0.78 0.15 75);
--coal: oklch(0.2 0.01 75);
--card: oklch(0.25 0.015 75);
font-family: system-ui,sans-serif;
background: var(--coal);
padding: 1.4rem;
display: flex;
flex-direction: column;
gap: .8rem;
}
.cgl-12 .cgl-12__label {
font-family: ui-monospace,Menlo,monospace;
font-size: .68rem;
color: color-mix(in oklab,var(--amber) 70%,white);
letter-spacing: .08em;
}
.cgl-12 .cgl-12__grid {
--floor: 200px;
--pad: 1.1rem;
--fs: .95rem;
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fit,minmax(min(var(--floor),100%),1fr));
gap: 12px;
margin-block-end: .6rem;
}
.cgl-12 .cgl-12__grid:has(>:nth-child(5)) {
--floor: 104px;
--pad: .6rem;
--fs: .78rem;
gap: 8px;
}
.cgl-12 .cgl-12__grid li {
background: var(--card);
border: 1px solid color-mix(in oklab,var(--amber) 25%,var(--card));
border-radius: 12px;
padding: var(--pad);
display: flex;
flex-direction: column;
gap: .4rem;
}
.cgl-12 .cgl-12__grid li strong {
font-size: var(--fs);
color: var(--amber);
}
.cgl-12 .cgl-12__grid li span {
font-size: .78rem;
line-height: 1.5;
color: oklch(0.75 0.02 75);
}
.cgl-12 .cgl-12__grid:has(>:nth-child(5)) li {
align-items: center;
text-align: center;
justify-content: center;
}
/* Legacy (pre-:has) equivalent: li:first-child:nth-last-child(n+5), li:first-child:nth-last-child(n+5) ~ li { ... } */
```
## JavaScript
```js
/* No JavaScript — .grid:has(> :nth-child(5)) detects the item count and flips custom properties for the compact state. */
```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: Quantity Query
Source: https://codefronts.com/layouts/css-grid-layouts/quantity-query/
A grid that restyles itself based on HOW MANY children it has — pure CSS, via :has() and child-counting selectors. Three tags render as roomy feature cards; six or more automatically compact into a dense chip wall. The CMS-proof layout: editors add items, the design responds, nobody files a ticket.
## HTML
```html
<section class="cgl-12" aria-label="Quantity query demo">
<p class="cgl-12__label">3 items → feature cards</p>
<ul class="cgl-12__grid">
<li><strong>Design</strong><span>Roomy, confident, editorial.</span></li>
<li><strong>Engineering</strong><span>Same class, same stylesheet.</span></li>
<li><strong>Research</strong><span>No modifiers, no JS.</span></li>
</ul>
<p class="cgl-12__label">8 items → the grid self-compacts via :has(> :nth-child(5))</p>
<ul class="cgl-12__grid">
<li><strong>Design</strong></li><li><strong>Engineering</strong></li><li><strong>Research</strong></li><li><strong>Ops</strong></li>
<li><strong>Data</strong></li><li><strong>Support</strong></li><li><strong>Legal</strong></li><li><strong>Growth</strong></li>
</ul>
</section>
```
## CSS
```css
.cgl-12,
.cgl-12 *,
.cgl-12 *::before,
.cgl-12 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.cgl-12 {
width: 100%;
min-height: 100vh;
--amber: oklch(0.78 0.15 75);
--coal: oklch(0.2 0.01 75);
--card: oklch(0.25 0.015 75);
font-family: system-ui,sans-serif;
background: var(--coal);
padding: 1.4rem;
display: flex;
flex-direction: column;
gap: .8rem;
}
.cgl-12 .cgl-12__label {
font-family: ui-monospace,Menlo,monospace;
font-size: .68rem;
color: color-mix(in oklab,var(--amber) 70%,white);
letter-spacing: .08em;
}
.cgl-12 .cgl-12__grid {
--floor: 200px;
--pad: 1.1rem;
--fs: .95rem;
list-style: none;
display: grid;
grid-template-columns: repeat(auto-fit,minmax(min(var(--floor),100%),1fr));
gap: 12px;
margin-block-end: .6rem;
}
.cgl-12 .cgl-12__grid:has(>:nth-child(5)) {
--floor: 104px;
--pad: .6rem;
--fs: .78rem;
gap: 8px;
}
.cgl-12 .cgl-12__grid li {
background: var(--card);
border: 1px solid color-mix(in oklab,var(--amber) 25%,var(--card));
border-radius: 12px;
padding: var(--pad);
display: flex;
flex-direction: column;
gap: .4rem;
}
.cgl-12 .cgl-12__grid li strong {
font-size: var(--fs);
color: var(--amber);
}
.cgl-12 .cgl-12__grid li span {
font-size: .78rem;
line-height: 1.5;
color: oklch(0.75 0.02 75);
}
.cgl-12 .cgl-12__grid:has(>:nth-child(5)) li {
align-items: center;
text-align: center;
justify-content: center;
}
/* Legacy (pre-:has) equivalent: li:first-child:nth-last-child(n+5), li:first-child:nth-last-child(n+5) ~ li { ... } */
```
## JavaScript
```js
/* No JavaScript — .grid:has(> :nth-child(5)) detects the item count and flips custom properties for the compact state. */
```How this works
The counting trick is .grid:has(> :nth-child(5)) — true only when a 5th child exists. That one selector gates a whole restyle: smaller minmax floor, tighter gap, compact typography. Before :has(), this needed JavaScript or the arcane li:first-child:nth-last-child(n+5) sibling chain (shown in the comment for legacy targets).
Both demo grids share ONE class and ONE stylesheet — the top grid has 3 items and renders as feature cards; the bottom has 8 and self-compacts. The quantity threshold, spacing and type scale all live in custom properties flipped by the :has() rule, so the two "designs" are one design with a counted state.
Make it yours
- Move the threshold by changing the nth-child index (5 → any count).
- Add a middle tier: chain two :has() gates (n+4 roomy, n+7 compact) for three density states.
- Flip more than layout — the pattern gates colour, radius, even content-visibility.
- Use the legacy sibling-count chain inside the :has() block if you must support 2022 browsers.
Gotchas — read before shipping
- :has() invalidates ancestor styles on DOM changes — cheap here, but don't hang thousand-node trees off quantity gates.
- Count DIRECT children only (the > combinator) — nested markup inflates counts silently.
- Design both states deliberately; auto-compacting into an unconsidered layout is worse than not compacting.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.2+ | 121+ | 111+ |
Floor set by :has(), oklch(), color-mix() as this demo is written. The core technique itself goes back further — Chrome 105+.
:has() is the gate — universal in evergreens since Dec 2023. The sibling-chain fallback works back to IE9 if you ever need it.