Grid

An opinionated grid system for React and Tailwind

GitHub

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.json

Then import it:

import { Grid, GridColumn } from "@/components/ui/grid"

Examples

Responsive span

base: 12 · md: 8
base: 12 · md: 4
base: 12 · md: 6
base: 12 · md: 6
<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

A
B
C
<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

Top left
Top right
Bottom
Sidebar
<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.

1st in DOM · 3rd on md
2nd in DOM · 1st on md
3rd in DOM · 2nd on md
<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

sm start: 4
md start: 2
lg start: 3
<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.

rowSpan: 2 on md
span: 4 on md
span: 4 on md
<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

PropTypeDefaultNotes
rowGapGapScale12Vertical gap
colGapGapScale8Horizontal gap
gapGapScale—Shorthand for both axes
asGridElement"div"Element to render

GridColumn

PropTypeDefaultNotes
spanGridSpan12Columns to span (1–12). Use 0 to hide at a breakpoint.
startGridStartautoColumn 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.
rowSpanGridRowSpan1Rows to span (1–12)
rowStartGridRowStartautoRow line the column starts on (1–12)
asGridElement"div"Element to render