Browse docs
Browse docs
A 2D layout primitive: rows AND columns. Pattern is MUI Grid v2 from the outside (<Grid container spacing={4}> + <Grid xs={12} md={6}>), but the engine is real CSS Grid under the hood — not flexbox.
import { Grid, Box, Typography } from '@dashforge/tw';
<Grid container spacing={6}>
<Grid xs={12} md={6} lg={4}>
<Box variant="outlined" rounded="xl" p={6}>
<Typography variant="h3">Card 1</Typography>
</Box>
</Grid>
<Grid xs={12} md={6} lg={4}>
<Box variant="outlined" rounded="xl" p={6}>
<Typography variant="h3">Card 2</Typography>
</Box>
</Grid>
<Grid xs={12} md={12} lg={4}>
<Box variant="outlined" rounded="xl" p={6}>
<Typography variant="h3">Card 3 — full row on md, third column on lg</Typography>
</Box>
</Grid>
</Grid>{/* Three equal columns, 4-unit gap */}
<Grid container spacing={4}>
<Grid xs={4}><Box variant="outlined" p={4}>1</Box></Grid>
<Grid xs={4}><Box variant="outlined" p={4}>2</Box></Grid>
<Grid xs={4}><Box variant="outlined" p={4}>3</Box></Grid>
</Grid>
{/* Responsive: 1 col mobile, 2 col tablet, 4 col desktop */}
<Grid container spacing={3}>
{items.map(i =>
<Grid key={i.id} xs={12} sm={6} lg={3}>
<Box variant="outlined" p={4}>{i.label}</Box>
</Grid>
)}
</Grid><Grid container> defaults to 12 columns, MUI convention. Override with cols={6}/cols={4}/etc. for denser dashboards.
<Grid> has two personalities, selected by the container prop:
| Role | When | Props |
|---|---|---|
| Container | <Grid container> | cols, spacing, spacingX, spacingY, autoFlow |
| Item | <Grid> (no container) | xs, sm, md, lg, xl |
TypeScript enforces the separation: <Grid container xs={6}> is a type error. <Grid xs={6} spacing={4}> is a type error. IntelliSense filters suggestions per role so you never see props that don't apply. This is the discriminated-union win over MUI.
MUI Grid v2 implements layout in flexbox (display: flex; flex-wrap: wrap) for historical IE11 reasons. We use real CSS Grid because:
gap — no negative-margin tricksgrid-template-columns: repeat(12, 1fr) + col-span-N maps directly to Tailwind utilitiesgrid-auto-flow: dense packs short items into gaps automaticallyThe external API is identical to MUI v2. Only the internals differ.
<Grid container spacing={4}>
<Grid xs={4}>One</Grid>
<Grid xs={4}>Two</Grid>
<Grid xs={4}>Three</Grid>
</Grid>Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
import { Grid, Box, Typography } from '@dashforge/tw';
<Grid container spacing={3}>
{Array.from({ length: 6 }).map((_, i) => (
<Grid key={i} xs={12} sm={6} md={4}>
<Box variant="outlined" p={4} rounded="md">
<Typography variant="body2" align="center">
Item {i + 1}
</Typography>
</Box>
</Grid>
))}
</Grid><Grid container spacing={3}>
<Grid xs={12} sm={6} lg={3}>A</Grid>
<Grid xs={12} sm={6} lg={3}>B</Grid>
<Grid xs={12} sm={6} lg={3}>C</Grid>
<Grid xs={12} sm={6} lg={3}>D</Grid>
</Grid>Breakpoint cascade is mobile-first (Tailwind convention): xs sets the base, sm/md/lg/xl override at the corresponding min-width.
xs=12 (full row)
xs=8 (main)
xs=4 (side)
3
3
3
3
import { Grid, Box, Typography } from '@dashforge/tw';
<Grid container spacing={2}>
<Grid xs={12}>
<Box variant="solid" color="primary" p={3} rounded="md">
<Typography variant="body2" align="center">xs=12 (full row)</Typography>
</Box>
</Grid>
<Grid xs={8}>
<Box variant="outlined" p={3} rounded="md">
<Typography variant="body2" align="center">xs=8 (main)</Typography>
</Box>
</Grid>
<Grid xs={4}>
<Box variant="outlined" p={3} rounded="md">
<Typography variant="body2" align="center">xs=4 (side)</Typography>
</Box>
</Grid>
{[1, 2, 3, 4].map((n) => (
<Grid key={n} xs={3}>
<Box variant="outlined" p={3} rounded="md">
<Typography variant="body2" align="center">3</Typography>
</Box>
</Grid>
))}
</Grid><Grid container spacing={6}>
<Grid xs={12} md={3}>
{/* Sidebar — full width mobile, 3/12 on md+ */}
<NavSidebar />
</Grid>
<Grid xs={12} md={9}>
{/* Main — full width mobile, 9/12 on md+ */}
<PageContent />
</Grid>
</Grid>'auto' and 'full'<Grid container>
<Grid xs="auto">Content-sized</Grid>
<Grid xs={6}>Half</Grid>
<Grid xs="full">Spans every column, regardless of cols count</Grid>
</Grid>'auto' lets the cell size to its content. 'full' spans every column — useful for full-width banners inside a multi-column grid.
{/* 6-col dashboard */}
<Grid container cols={6} spacing={4}>
<Grid xs={3}>Wide tile</Grid>
<Grid xs={3}>Wide tile</Grid>
<Grid xs={2}>Narrow tile</Grid>
<Grid xs={2}>Narrow tile</Grid>
<Grid xs={2}>Narrow tile</Grid>
</Grid>
{/* 4-col grid */}
<Grid container cols={4} spacing={3}>
<Grid xs={1}>A</Grid>
<Grid xs={1}>B</Grid>
<Grid xs={1}>C</Grid>
<Grid xs={1}>D</Grid>
</Grid>Accepted cols values: 1 · 2 · 3 · 4 · 6 · 12 (the divisors of 12, plus full-width).
spacingX / spacingY<Grid container cols={3} spacingX={8} spacingY={2}>
<Grid xs={1}>tight rows, wide columns</Grid>
<Grid xs={1}>...</Grid>
<Grid xs={1}>...</Grid>
<Grid xs={1}>...</Grid>
</Grid>spacingX and spacingY override the uniform spacing per axis. Useful when text rows feel cramped vertically but columns should breathe.
autoFlow — dense packing<Grid container cols={6} spacing={3} autoFlow="dense">
<Grid xs={1}>1</Grid>
<Grid xs={3}>2 — wide</Grid>
<Grid xs={1}>3</Grid>
<Grid xs={1}>4</Grid>
<Grid xs={4}>5 — wider</Grid>
<Grid xs={2}>6</Grid>
</Grid>With autoFlow="dense", CSS Grid back-fills gaps by reordering items — no holes in tile layouts. Use "col" for column-major flow, "row-dense" / "col-dense" to combine direction with dense packing.
| Scenario | Choose | Why |
|---|---|---|
| Vertical list of form fields | <Stack gap={3}> | 1D, no column math |
Header [logo] [spacer] [actions] | <Stack direction="row" justify="between"> | 1D with main-axis spacing |
| Toolbar (buttons in a row) | <Stack direction="row" gap={2}> | 1D, fixed gap |
| 3 cards side-by-side, equal width | <Grid container><Grid xs={4}> | 2D, percent-based |
| Responsive card grid (1/2/4 cols) | <Grid container><Grid xs={12} sm={6} lg={3}> | 2D, breakpoint-driven |
| Sidebar 25% + content 75% | <Grid container><Grid xs={3}><Grid xs={9}> | 2D, proportions |
| Sidebar 300px + content fill | <Stack direction="row"> + sx="w-[300px]" | 1D, no column math |
| Dashboard with mixed tile widths | <Grid container cols={6} autoFlow="dense"> | 2D with packing |
The rule: 1 axis → Stack, 2 axes → Grid. Asymmetric flex with one fixed + one fill stays in Stack — it doesn't need column math.
as and asChild{/* Container as <section> */}
<Grid container as="section" spacing={4}>
<Grid xs={6}>One</Grid>
<Grid xs={6}>Two</Grid>
</Grid>
{/* Item asChild — Grid styles paint onto the child */}
<Grid xs={12} md={6} asChild>
<article>
<Box variant="outlined" p={6}>...</Box>
</article>
</Grid>Container asChild is technically allowed but rarely useful — display: grid only applies to direct children, so the slotted element has to already be a viable container parent. Use sparingly.
container={true})| Prop | Type | Default | Description |
|---|---|---|---|
container | true (literal) | — | Discriminator. |
cols | 1 | 2 | 3 | 4 | 6 | 12 | 12 | Number of columns. Divisors of 12 only. |
spacing | GridSpacingStep | — | Uniform gap. |
spacingX | GridSpacingStep | — | Horizontal gap (overrides spacing on X). |
spacingY | GridSpacingStep | — | Vertical gap (overrides spacing on Y). |
autoFlow | 'row' | 'col' | 'dense' | 'row-dense' | 'col-dense' | — | grid-auto-flow. |
container)| Prop | Type | Default | Description |
|---|---|---|---|
xs | ColSpan | 'full' | Base breakpoint span (no media query). |
sm | ColSpan | — | Span at sm+ (≥640px). |
md | ColSpan | — | Span at md+ (≥768px). |
lg | ColSpan | — | Span at lg+ (≥1024px). |
xl | ColSpan | — | Span at xl+ (≥1280px). |
| Prop | Type | Default | Description |
|---|---|---|---|
as | ElementType | 'div' | Override the HTML tag. |
asChild | boolean | false | Render via Radix Slot onto the single child. Wins over as. |
sx | string | — | Utility-class override, merged via tailwind-merge. |
| ...rest | HTMLAttributes<HTMLDivElement> | — | Native attributes pass through. |
import type { ColSpan, GridSpacingStep, GridContainerProps, GridItemProps } from '@dashforge/tw';ColSpan = 1..12 | 'auto' | 'full'GridSpacingStep = 0 | '0.5' | 1 | 2 | 3 | 4 | 6 | 8 | 12 | 16 | 24 (mirror of Box/Stack spacing)import { gridVariants } from '@dashforge/tw';
// gridVariants({ container: true, cols: 12, spacing: 4 }) → classNamedisplay: grid + grid-template-columns: repeat(N, 1fr) + col-span-N. Not flexbox. Faster mental model, native gap, no negative-margin tricks.cols = 12 on containers (MUI v2 convention). Override with cols={6} etc. for denser dashboards. We restrict to divisors of 12 so the math stays clean.xs = 'full' on items. A forgotten breakpoint prop produces a full-width cell — visually obvious rather than mysteriously squashed.<Grid container xs={6}> is a compile error. So is <Grid xs={6} spacing={4}>. The IntelliSense narrows by role.cols, spacing, xs, etc. are stripped before spreading native attributes. No React unknown-attribute warnings.<Box variant="outlined"> to give them card chrome.<Stack direction="row"> with explicit child widths. Grid's column math gets in the way.