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.
Configure <Grid container> defaults application-wide. Only container-role axes are exposed to theme configuration — item breakpoints (xs, sm, md, lg, xl) are per-instance responsive layout choices and stay per-usage.
import { patchTheme } from '@dashforge/tw-theme';
patchTheme({
components: {
Grid: {
defaults: { cols: 12, spacing: 4 },
},
},
});Configurable axes (GridVariantProps):
| Axis | Type | Notes |
|---|---|---|
cols | 1 | 2 | 3 | 4 | 6 | 12 | Column count for the grid. |
spacing | GridSpacingStep | Both-axis gap. Shorthand for spacingX + spacingY. |
spacingX | GridSpacingStep | Horizontal gap override. |
spacingY | GridSpacingStep | Vertical gap override. |
autoFlow | 'row' | 'col' | 'dense' | 'row-dense' | 'col-dense' | grid-auto-flow. |
Not theme-configurable: item-role breakpoints (xs, sm, md, lg, xl) — those are per-instance responsive choices, not identity.
Precedence chain (lowest → highest):
cols falls back to 12 in the component implementation).theme.components.Grid.defaults — application-wide override.<Grid container cols={4} />) — wins over theme.sx — wins via tailwind-merge.Item-role <Grid xs={6} md={3}> never consumes theme container defaults (verified by regression test).
container={true})| Prop | Type | Default | Description |
|---|---|---|---|
container | true | — | Discriminator — true makes this a grid container. |
autoFlow | 'row' | 'col' | 'dense' | 'row-dense' | 'col-dense' | — | grid-auto-flow — useful for dense or column-major packing. |
cols | 1 | 2 | 3 | 4 | 6 | 12 | — | Number of columns. Default 12. |
spacing | GridSpacingStep | — | Gap between cells. Shorthand for spacingX + spacingY. |
spacingX | GridSpacingStep | — | Horizontal gap override (wins over spacing). |
spacingY | GridSpacingStep | — | Vertical gap override (wins over spacing). |
container)| Prop | Type | Default | Description |
|---|---|---|---|
container | false | — | Discriminator — absent (or false) makes this a grid item. |
lg | ColSpan | — | Span at lg breakpoint and up. |
md | ColSpan | — | Span at md breakpoint and up. |
sm | ColSpan | — | Span at sm breakpoint and up. |
xl | ColSpan | — | Span at xl breakpoint and up. |
xs | ColSpan | — | Base breakpoint (no media query). Default 'full'. |
as, asChild, sx — plus any native HTMLAttributes<HTMLDivElement> — apply on both roles. Configured via GridCommonProps; see grid.types.ts for the full signature.
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)<Grid> has no slots. It's a discriminated-union primitive that renders a single DOM element in either container or item role — there is no anatomy to target separately. Compose your layout using <Grid item xs={6}> children of a <Grid container> parent.
The slotProps API pattern only exists on compound components with multiple visually distinct regions. Examples: <Menu>, <DataGrid>, <Autocomplete>.
For <Grid>, use sx on either the container or specific items for any style override.
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.