Grid

The collection arranger — uniform tiles that wrap and reflow their column count with the container width, no breakpoints. A peer of Stack.

Each example measures itself, so its gutters are to scale rather than to the page's. The arrangements are exact.

Grid is repeat(auto-fit, minmax(minColumnWidth, 1fr)): as many equal columns ≥ minColumnWidth as fit, then wrap. It reads its own resolved width, so the same props give more columns in a wide container and fewer in a narrow one — container-relative for free.

Grid arranges; the element says what is being arranged. By default that is a list — a <ul> with an <li> round each child, announced as “list, 6 items” — because a reflowing set of peers usually is one. Pass the items, never their <li>s. When the content is not a list, as="div" keeps the layout and drops the list.

Generating children’s markup is earned rather than assumed: Grid already wraps every child in ratio mode, so it has a per-child wrapper to hand the <li> to — and with a ratio set the <li> becomes that cell rather than joining it, so the list costs no extra element. Because the <li> is the grid item, it hands a stretched row on to its child, so cards in a row still come out equal height. Slot as="ul" still asks you for the <li>s, because a Slot’s children are arbitrary content rather than items and it has nowhere to put them.

Unstyled mechanics only — drop designed components straight in. Chain rule: Section > Stack > Grid (the Stack owns the cap). Every dial is live in Properties; the examples below show what a single control can't — the same Grid in two contexts, two gaps that mean different things, a difference that only appears when a row is part-full, and a Grid that is not a list.

Same Grid, different width

Auto-fit reads the Grid's own resolved width, so nothing has to be told.

Identical props to the example at the top — same minColumnWidth, same gap, same six tiles — now sharing a 1|1 Stack with an empty Slot. The column count fell with the lane, so a component laid out once behaves correctly wherever it lands.

The Grid is a direct child of the Stack, with no Slot around it. A Slot exists to make one grid item out of content that isn't one; a Grid already arranges its own children, so it is the region.

Gap — layout spacing

The collection reads as regions of the page.

gapScale="layout" draws from the same fluid scale the Stack uses, so all three gaps here are one value: between the two regions above, between them and the collection, and between the tiles. Right for a few large feature cards.

Gap — component spacing

The same tiles read as one object sitting in the layout.

gapScale="component" is a fixed 4px that stays crisp at every width and deliberately ignores the rhythm around it — a product shelf, a logo row.

Nothing else changed between these two bands. Same tiles, same floor, same arrangement — the gutter is the only variable, and the two gutters answer to different things: one to the layout around the collection, one to the collection itself. Neither is the correct answer. The test is what the gap is doing — internal cohesion, or layout rhythm.

Min and max

maxColumns governs the wide end, minColumnWidth the narrow one.

Identical Grids in a 2|1 Stack, both maxColumns={3}. The wide lane would fit more at this floor and is held to the ceiling; the narrow lane never reaches it and takes what it can fit. Two dials answering two different questions, meeting at one width.

It is a ceiling, not a count: asking for more columns than fit changes nothing. And there are no breakpoints involved, so the Grid still answers to its own lane. A columns prop at breakpoints could not — container queries resolve against the Section, so both lanes here would be told the same number and the narrow one would overflow.

Stretch

It only shows when the whole collection is smaller than one row's capacity.

Two tiles each, in lanes with room for more. On the left, stretch (the default) collapses the unused columns, so the two tiles grow to fill the lane. On the right, stretch={false} keeps those columns, so the tiles hold their column width and the unused room stays visible — right for a collection that shrinks, like a filtered list down to two, where ballooning cards would read as a different design.

A column collapses when nothing in the grid occupies it, not when a row runs short — so 8 tiles in 3 columns look identical either way, last row included. That is why it earns an example rather than being left to the control: toggled on a full row it looks like a prop that does nothing.

Grid as a div

The layout without the list, for content that is not one.

Regions are not a list. An article and an aside want exactly what Grid does — equal areas that reflow with the lane, no breakpoints — but they are two different things, not two of one, and “list, 2 items” would announce them as a collection. as="div" keeps the reflow and drops the list: the Grid is a <div> and each child is the grid item itself, nothing wrapped round it.

Reach for it when the content is a set of regions rather than a set of peers — anything you would not describe as “a list of”. as="ol" is the other direction: still a list, and the order means something.

Item is the word for a grid’s direct child in both vocabularies — CSS calls it a grid item, HTML a list item — and it is what the guide overlay labels on the examples above. Tile appears in the prose describing how those examples look; it is not a second name for the box, and it would over-claim over a grid of large feature cards.