Props reference
Grid
| Prop | Type | Default | Description |
|---|---|---|---|
cols | 1–12 | "auto" | "masonry" | 1 | Column count. "auto" = fluid fill. "masonry" = CSS columns. |
rows | 1–12 | "auto" | "auto" | Explicit row count. Leave "auto" unless you need a fixed grid. |
gap | "none"|"xs"|"sm"|"md"|"lg"|"xl"|"2xl" | "md" | Uniform gap. |
gapX | same scale | — | Horizontal gap override. |
gapY | same scale | — | Vertical gap override. |
align | "start"|"center"|"end"|"stretch" | "stretch" | align-items |
justify | "start"|"center"|"end"|"stretch"|"between"|"around"|"evenly" | "stretch" | justify-items / justify-content |
flow | "row"|"col"|"dense"|"row-dense"|"col-dense" | "row" | grid-auto-flow |
minColWidth | string | "240px" | Minimum column width for cols="auto". |
depth | number | 0 | Recursion depth. Auto-incremented mentally — pass manually if needed. |
as | string | "div" | HTML element to render. |
full | boolean | true | Adds w-full. |
prose | boolean | false | Adds max-w-prose mx-auto for text layouts. |
class | string | — | Extra Tailwind classes. |
GridItem
| Prop | Type | Default | Description |
|---|---|---|---|
colSpan | 1–12 | "full" | "auto" | "auto" | Column span. |
rowSpan | 1–6 | "full" | "auto" | "auto" | Row span. |
colStart | 1–13 | "auto" | "auto" | Explicit column start line. |
colEnd | 1–13 | "auto" | "auto" | Explicit column end line. |
rowStart | 1–7 | "auto" | "auto" | Explicit row start line. |
rowEnd | 1–7 | "auto" | "auto" | Explicit row end line. |
align | "start"|"center"|"end"|"stretch" | "stretch" | align-self |
justify | "start"|"center"|"end"|"stretch" | "stretch" | justify-self |
as | string | "div" | HTML element. |
Recipes
1. Simple 3-column card grid
<Grid cols={3} gap="lg">
<Card title="One" />
<Card title="Two" />
<Card title="Three" />
</Grid>
Responsive: collapses → 2 cols at sm → 1 col on mobile.
2. Fluid auto-fill (Pinterest-style)
<Grid cols="auto" minColWidth="280px" gap="md" align="start">
{posts.map((p) => <PostCard entry={p} />)}
</Grid>
Fills as many columns as fit at 280px minimum.
3. Masonry (CSS columns)
<Grid cols="masonry" gap="md" class="columns-1 sm:columns-2 lg:columns-3">
{items.map((i) => <Item data={i} />)}
</Grid>
Uses CSS columns rather than grid. Items flow top-to-bottom through columns.
Override column count with Tailwind columns-* via class.
4. Feature layout — spanning hero cell
<Grid cols={3} gap="lg">
<GridItem colSpan={2} rowSpan={2}>
<HeroCard />
</GridItem>
<SmallCard />
<SmallCard />
</Grid>
5. Explicit placement (magazine layout)
<Grid cols={4} rows={3} gap="sm" flow="dense">
<GridItem colStart={1} colSpan={2} rowStart={1} rowSpan={2}>
<Feature />
</GridItem>
<GridItem colStart={3} colSpan={2}>
<Article />
</GridItem>
<GridItem colSpan="full" rowStart={3}>
<Banner />
</GridItem>
</Grid>
flow="dense" backfills gaps automatically.
6. Recursive nested grids
<Grid cols={2} gap="xl">
{/* Left: nested 2×2 sub-grid */}
<Grid cols={2} gap="sm" depth={1}>
<Card /><Card />
<Card /><Card />
</Grid>
{/* Right: single tall card */}
<GridItem align="stretch">
<TallCard />
</GridItem>
</Grid>
Nested Grid at depth={1} automatically reduces gap by one step (xl →
lg). Depth 2+ reduces by two steps (floor: xs).
You can pass depth manually to skip the auto-reduction:
<Grid cols={3} gap="md" depth={0}> {/* stays "md" regardless */}</Grid>
7. Semantically correct lists
<Grid as="ul" cols={3} gap="md">
{
items.map((i) => (
<GridItem as="li">
<Card data={i} />
</GridItem>
))
}
</Grid>
8. Asymmetric sidebar layout
<Grid cols={12} gap="lg" align="start">
<GridItem colSpan={3}>
<Sidebar />
</GridItem>
<GridItem colSpan={9}>
<MainContent />
</GridItem>
</Grid>
9. In [slug].astro — replacing ContentList sections
{
hasFeaturedContent && (
<section class="container mx-auto px-4 py-12">
<h2 class="mb-6 text-2xl font-bold">Featured</h2>
<Grid cols="auto" minColWidth="320px" gap="lg" align="start">
{[...projects, ...posts].map((e) => (
<ContentCard entry={e} urlMap={urlMap} />
))}
</Grid>
</section>
)
}
Depth and the CSS custom property
Every Grid writes --grid-depth as an inline CSS custom property and
data-grid-depth as a data attribute. Use these for depth-aware styling without
JS:
/* In your global CSS */
[data-grid-depth="0"] > * {
/* root grid cells */
border-radius: 1rem;
}
[data-grid-depth="1"] > * {
/* nested grid cells */
border-radius: 0.5rem;
}
/* Or via CSS custom property */
.my-card {
border-radius: calc(1rem - var(--grid-depth, 0) * 0.25rem);
}
Tailwind v4 notes
- Arbitrary column templates work:
grid-cols-[repeat(auto-fill,minmax(280px,1fr))] - All class lookups use string literals — no
safelistneeded since classes are hardcoded in the component (not dynamically constructed with string interpolation) twMergehandles conflicts when consumer passesclassoverridesdata-grid-depthand--grid-depthsurvive Astro’s scoped style boundary since they’re inline attributes/styles
Caveats
Masonry column count: cols="masonry" defaults to a responsive
columns-1 sm:columns-2 lg:columns-3. Override with class:
<Grid cols="masonry" class="columns-2 md:columns-4" />
Depth prop is manual: Astro cannot automatically pass props through
arbitrary slot children. Increment depth by hand when nesting:
<Grid cols={2} depth={0}>
<Grid cols={3} depth={1}>
← you write depth={1}
<Grid cols={2} depth={2}> ← and depth={2}</Grid></Grid
></Grid
>
gapX/gapY disable auto-reduction: If you set either explicitly,
depth-aware gap reduction does not apply (your explicit value wins).