PRIMITIVE
Hairline grid
A reflowing grid whose cells are separated by exactly one hairline. No double borders, no gaps at the edges. It is the signature layout device of this design, and the fiddliest thing here to get right.
Four cells
The container draws its top and left edges; each cell draws its right and bottom. Neighbours share one line, the outer frame closes, and nothing doubles up.
Cells share one hairline. No double borders.
The container draws top and left.
Each cell draws right and bottom.
So the frame closes and nothing doubles.
The same three cells with .hairline-grid-fixed: the fourth track stays, so a cell is a cell.
auto-fill, not auto-fit.
The empty track keeps its width.
So this cell is the same size as one in a full row.
<div class="hairline-grid" style="--cell:205px; width:100%">
<div class="hairline-cell">
<span class="section-head-kicker">01</span>
<p class="placeholder-caption">Cells share one hairline. No double borders.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">02</span>
<p class="placeholder-caption">The container draws top and left.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">03</span>
<p class="placeholder-caption">Each cell draws right and bottom.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">04</span>
<p class="placeholder-caption">So the frame closes and nothing doubles.</p>
</div>
</div>
<p class="placeholder-caption" style="margin-block:24px 8px">The same three cells with <code>.hairline-grid-fixed</code>: the fourth track stays, so a cell is a cell.</p>
<div class="hairline-grid hairline-grid-fixed" style="--cell:205px; width:100%">
<div class="hairline-cell">
<span class="section-head-kicker">01</span>
<p class="placeholder-caption">auto-fill, not auto-fit.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">02</span>
<p class="placeholder-caption">The empty track keeps its width.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">03</span>
<p class="placeholder-caption">So this cell is the same size as one in a full row.</p>
</div>
</div>Cells on their own surface
hairline-grid-raised puts every cell on the raised role. A mesh on the page ground is one plane with divisions in it; a mesh of raised cells is separate panels that happen to share their edges. Both are right and they say different things, which is why it is a variant rather than a default.
Raised cells read as separate panels sharing their edges.
A mesh on the page ground reads as one plane divided.
Each cell draws right and bottom.
So the frame closes and nothing doubles.
The same three cells with .hairline-grid-fixed: the fourth track stays, so a cell is a cell.
auto-fill, not auto-fit.
The empty track keeps its width.
So this cell is the same size as one in a full row.
<div class="hairline-grid hairline-grid-raised" style="--cell:205px; width:100%">
<div class="hairline-cell">
<span class="section-head-kicker">01</span>
<p class="placeholder-caption">Raised cells read as separate panels sharing their edges.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">02</span>
<p class="placeholder-caption">A mesh on the page ground reads as one plane divided.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">03</span>
<p class="placeholder-caption">Each cell draws right and bottom.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">04</span>
<p class="placeholder-caption">So the frame closes and nothing doubles.</p>
</div>
</div>
<p class="placeholder-caption" style="margin-block:24px 8px">The same three cells with <code>.hairline-grid-fixed</code>: the fourth track stays, so a cell is a cell.</p>
<div class="hairline-grid hairline-grid-fixed" style="--cell:205px; width:100%">
<div class="hairline-cell">
<span class="section-head-kicker">01</span>
<p class="placeholder-caption">auto-fill, not auto-fit.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">02</span>
<p class="placeholder-caption">The empty track keeps its width.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">03</span>
<p class="placeholder-caption">So this cell is the same size as one in a full row.</p>
</div>
</div>Cells that hold a picture
Give a cell .hairline-cell-flush and it drops its padding, so a picture reaches the hairline while the caption below keeps its inset.
The picture reaches the hairline; the words do not.
A flush cell owns its own padding.
<div class="hairline-grid" style="--cell:180px; width:100%">
<div class="hairline-cell hairline-cell-flush">
<div class="placeholder" style="--ratio:4/5; border:0">
<span class="placeholder-kicker">PLACEHOLDER</span>
<span class="placeholder-note">4:5</span>
</div>
<div style="padding:22px 24px; border-top:var(--ds-hairline) solid var(--ds-rule)">
<span class="section-head-kicker">M01</span>
<p class="placeholder-caption" style="margin-top:8px">The picture reaches the hairline; the words do not.</p>
</div>
</div>
<div class="hairline-cell hairline-cell-flush">
<div class="placeholder placeholder-warm" style="--ratio:4/5; border:0">
<span class="placeholder-kicker">PLACEHOLDER</span>
<span class="placeholder-note">4:5</span>
</div>
<div style="padding:22px 24px; border-top:var(--ds-hairline) solid var(--ds-rule)">
<span class="section-head-kicker">M02</span>
<p class="placeholder-caption" style="margin-top:8px">A flush cell owns its own padding.</p>
</div>
</div>
</div>Collapsed into a band
Inside a shell-sunk with no padding between them, this grid's own top rule lands a pixel under the band's β two hairlines inside the gutters and one outside. .hairline-grid-banded hands both horizontals to the band, so the cells touch the section's edges.
No top rule of its own β the band drew it.
Row dividers stay, so a wrapped second row still separates.
The last row's bottom rule lands on the band's.
One hairline at each end, not two a pixel apart.
<section class="shell-flush shell-sunk">
<div class="shell">
<div class="hairline-grid hairline-grid-banded" style="--cell:205px">
<div class="hairline-cell">
<span class="section-head-kicker">01</span>
<p class="placeholder-caption">No top rule of its own β the band drew it.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">02</span>
<p class="placeholder-caption">Row dividers stay, so a wrapped second row still separates.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">03</span>
<p class="placeholder-caption">The last row's bottom rule lands on the band's.</p>
</div>
<div class="hairline-cell">
<span class="section-head-kicker">04</span>
<p class="placeholder-caption">One hairline at each end, not two a pixel apart.</p>
</div>
</div>
</div>
</section>Reference
Read from primitives/hairline-grid.css when this page was built, so it cannot fall behind the stylesheet.
.hairline-grid.hairline-cell.hairline-grid-raised.hairline-cell-flush.hairline-grid-banded.hairline-grid-fixed.hairline-grid-tight--ds-cell-min--ds-cell-min-tight--ds-hairline--ds-pad-card--ds-radius--ds-raised--ds-ruleNotes
- Adding a gap. The page ground shows between cells, the hairlines stop meeting at the corners, and the whole effect becomes a set of floating boxes. If you want space, you want a different primitive.
- Dropping the inner min(). The track is
minmax(min(100%, var(--cell)), 1fr). Without themin(100%, β¦), a 205px floor on a 180px screen makes the track wider than the viewport and the whole page scrolls sideways. - Forgetting min-width: 0. Grid items default to
min-width: autoand refuse to shrink below their content, so one long unbroken string in one cell widens every track.
This one shipped with a defect and Greg found it by looking, inside a minute. The base grid draws its own top and left, which is safe while auto-fit guarantees there is never an empty track for them to hang over. .hairline-grid-fixed keeps the tracks it cannot fill β so the container's top rule ran on across the empty column with no cell under it. A line sticking out of the corner of the grid.
There is no version where a container draws a line and the line stops early: a border spans its box, and CSS cannot say which cell ends a row. So the variant moves the whole frame on to the CELLS β each draws all four of its own edges and overlaps its neighbour's by exactly one hairline, which is what the two negative margins do. Shared edges land on the same pixels, an empty track draws nothing at all, and the frame closes around whatever is actually there. Measured: the seam between two cells is β1px, so the two borders are one line.
Column width is a property of the content, not of the device, so it is set per instance with --cell. Use .hairline-grid-tight for a rail of small equal things, and .hairline-grid-fixed when several grids share a page and the CELL is the unit a reader compares. That one swaps auto-fit for auto-fill, so an empty track keeps its width instead of handing it to the cells that exist β five groups of 3, 4, 4, 3, 3 read as one system at a fixed width and as five different designs when every row stretches to fill.
.hairline-grid-banded names its container, which is usually a smell and here is the point. The frame is shared, so one of the two has to know β and a band that inspected its contents instead would have to draw its rules at the shell's width rather than the window's, which is the one thing a band exists not to do. On the page ground it opens the frame and leaves the cells hanging; that is the same fact from the other side.