Grid
An opinionated grid system for React and Tailwind
Why Grid
Responsive layouts and Tailwind are everyday tools for building modern websites. Combining them isn’t — two patterns keep getting in the way:
- Layout gets lost in the class string. Spans, gaps, and breakpoints sit alongside every other utility, and a grid’s container and its columns are both just a
<div>with classes. You can’t see the layout at a glance. - Tailwind’s breakpoints stop at the component boundary. Reach for a library grid like MUI’s and you redeclare breakpoints in its theme — two configs to keep in sync, and one more provider wrapping your app.
Grid brings a 12-column grid system to Tailwind, built on CSS grid: Grid for containers, GridColumn for columns. Type-safe responsive props, plain Tailwind under the hood, copy-paste install. No runtime, no dependencies, no config.
Installation
Add Grid to your project via the shadcn CLI.
pnpm dlx shadcn@latest add https://grid.joohyunpark.com/registry/grid.jsonThen import it:
import { Grid, GridColumn } from "@/components/ui/grid"Examples
Responsive span
<Grid>
<GridColumn span={{ base: 12, md: 8 }}>...</GridColumn>
<GridColumn span={{ base: 12, md: 4 }}>...</GridColumn>
<GridColumn span={{ base: 12, md: 6 }}>...</GridColumn>
<GridColumn span={{ base: 12, md: 6 }}>...</GridColumn>
</Grid>Responsive gap
<Grid gap={{ base: 2, sm: 4, md: 8 }}>
<GridColumn span={4}>A</GridColumn>
<GridColumn span={4}>B</GridColumn>
<GridColumn span={4}>C</GridColumn>
</Grid>Nested grids
<Grid>
<GridColumn span={{ md: 8 }}>
<Grid gap={4}>
<GridColumn span={6}>Top left</GridColumn>
<GridColumn span={6}>Top right</GridColumn>
<GridColumn span={12}>Bottom</GridColumn>
</Grid>
</GridColumn>
<GridColumn span={{ md: 4 }}>Sidebar</GridColumn>
</Grid>Reordering
Grid leaves visual order to Tailwind’s order-* utilities. Pass them via className — they take the same responsive prefixes (sm:, md:, …) as any other Tailwind class.
<Grid gap={4}>
<GridColumn span={{ base: 12, md: 4 }} className="md:order-3">...</GridColumn>
<GridColumn span={{ base: 12, md: 4 }} className="md:order-1">...</GridColumn>
<GridColumn span={{ base: 12, md: 4 }} className="md:order-2">...</GridColumn>
</Grid>Start
<Grid gap={4}>
<GridColumn span={{ sm: 6 }} start={{ sm: 4 }}>...</GridColumn>
<GridColumn span={{ md: 4 }} start={{ md: 2 }}>...</GridColumn>
<GridColumn span={{ lg: 4 }} start={{ lg: 3 }}>...</GridColumn>
</Grid>Row span
rowSpan lets a column cover several rows, and the columns after it fill in beside it. Rows size to their content, so it works best when the columns beside it are of similar height.
<Grid gap={4}>
<GridColumn span={{ base: 12, md: 8 }} rowSpan={{ md: 2 }}>...</GridColumn>
<GridColumn span={{ base: 12, md: 4 }}>...</GridColumn>
<GridColumn span={{ base: 12, md: 4 }}>...</GridColumn>
</Grid>API reference
type Breakpoint = "base" | "sm" | "md" | "lg" | "xl" | "2xl"
type ResponsiveValue<T> = T | Partial<Record<Breakpoint, T>>
type GapScale = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12
type GridSpan = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
type GridStart = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
type GridRowSpan = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
type GridRowStart = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
type GridElement = "div" | "section"Every prop except as accepts a single value or a per-breakpoint object (e.g. { md: 4, lg: 6 }). GapScale follows Tailwind’s spacing scale.
Grid
| Prop | Type | Default | Notes |
|---|---|---|---|
rowGap | GapScale | 12 | Vertical gap |
colGap | GapScale | 8 | Horizontal gap |
gap | GapScale | — | Shorthand for both axes |
as | GridElement | "div" | Element to render |
GridColumn
| Prop | Type | Default | Notes |
|---|---|---|---|
span | GridSpan | 12 | Columns to span (1–12). Use 0 to hide at a breakpoint. |
start | GridStart | auto | Column line the column starts on (1–12). Wraps to the next row if that column is taken; span is capped so it never runs past the last column. |
rowSpan | GridRowSpan | 1 | Rows to span (1–12) |
rowStart | GridRowStart | auto | Row line the column starts on (1–12) |
as | GridElement | "div" | Element to render |