Base
Grid
Grid and GridItem wrap CSS grid with typed, fully-static Tailwind class maps for column counts, spans, and gaps at every breakpoint.
Installation
npx shadcn add https://rck.vibeboxph.com//r/grid.jsonLive preview & controls
1
2
3
4
5
6
7
8
Controls
<Grid cols={4} gap={4}>
<GridItem>1</GridItem>
{/* … */}
</Grid>Anatomy
GridThe grid container.GridItemA grid child with an optional responsive column span.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| cols | 1-12 | 12 | Base column count. |
| colsSm / colsMd / colsLg / colsXl | 1-12 | — | Column count from each breakpoint up. |
| gap | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 16 | 4 | Grid gap. |
| span (GridItem) | 1-12 | "full" | "auto" | — | Base column span. |
| spanSm / spanMd / spanLg / spanXl (GridItem) | 1-12 | "full" | "auto" | — | Column span from each breakpoint up. |
Accessibility
- Purely layout — renders plain <div> elements and doesn't alter document semantics or reading order.
- Keep the DOM order matching the intended reading order; grid placement is visual only.
Source
registry/rck/ui/grid.tsx
import * as React from "react";
import { cn } from "@/lib/utils";
type ColCount = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
type Span = ColCount | "full" | "auto";
type Gap = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 16;
/**
* Every responsive utility below is written as a full literal class name
* (never built via string concatenation) so Tailwind's static analyzer can
* always find it, whether this file lives in this repo or gets copied into
* a consumer project.
*/
const colsMap: Record<ColCount, string> = {
1: "grid-cols-1",
2: "grid-cols-2",
3: "grid-cols-3",
4: "grid-cols-4",
5: "grid-cols-5",
6: "grid-cols-6",
7: "grid-cols-7",
8: "grid-cols-8",
9: "grid-cols-9",
10: "grid-cols-10",
11: "grid-cols-11",
12: "grid-cols-12",
};
const colsSmMap: Record<ColCount, string> = {
1: "sm:grid-cols-1",
2: "sm:grid-cols-2",
3: "sm:grid-cols-3",
4: "sm:grid-cols-4",
5: "sm:grid-cols-5",
6: "sm:grid-cols-6",
7: "sm:grid-cols-7",
8: "sm:grid-cols-8",
9: "sm:grid-cols-9",
10: "sm:grid-cols-10",
11: "sm:grid-cols-11",
12: "sm:grid-cols-12",
};
const colsMdMap: Record<ColCount, string> = {
1: "md:grid-cols-1",
2: "md:grid-cols-2",
3: "md:grid-cols-3",
4: "md:grid-cols-4",
5: "md:grid-cols-5",
6: "md:grid-cols-6",
7: "md:grid-cols-7",
8: "md:grid-cols-8",
9: "md:grid-cols-9",
10: "md:grid-cols-10",
11: "md:grid-cols-11",
12: "md:grid-cols-12",
};
const colsLgMap: Record<ColCount, string> = {
1: "lg:grid-cols-1",
2: "lg:grid-cols-2",
3: "lg:grid-cols-3",
4: "lg:grid-cols-4",
5: "lg:grid-cols-5",
6: "lg:grid-cols-6",
7: "lg:grid-cols-7",
8: "lg:grid-cols-8",
9: "lg:grid-cols-9",
10: "lg:grid-cols-10",
11: "lg:grid-cols-11",
12: "lg:grid-cols-12",
};
const colsXlMap: Record<ColCount, string> = {
1: "xl:grid-cols-1",
2: "xl:grid-cols-2",
3: "xl:grid-cols-3",
4: "xl:grid-cols-4",
5: "xl:grid-cols-5",
6: "xl:grid-cols-6",
7: "xl:grid-cols-7",
8: "xl:grid-cols-8",
9: "xl:grid-cols-9",
10: "xl:grid-cols-10",
11: "xl:grid-cols-11",
12: "xl:grid-cols-12",
};
const gapMap: Record<Gap, string> = {
0: "gap-0",
1: "gap-1",
2: "gap-2",
3: "gap-3",
4: "gap-4",
5: "gap-5",
6: "gap-6",
8: "gap-8",
10: "gap-10",
12: "gap-12",
16: "gap-16",
};
export interface GridProps extends React.HTMLAttributes<HTMLDivElement> {
/** Base column count (applies at every breakpoint unless overridden). */
cols?: ColCount;
/** Column count from the `sm` breakpoint up. */
colsSm?: ColCount;
/** Column count from the `md` breakpoint up. */
colsMd?: ColCount;
/** Column count from the `lg` breakpoint up. */
colsLg?: ColCount;
/** Column count from the `xl` breakpoint up. */
colsXl?: ColCount;
gap?: Gap;
}
export const Grid = React.forwardRef<HTMLDivElement, GridProps>(
({ className, cols = 12, colsSm, colsMd, colsLg, colsXl, gap = 4, ...props }, ref) => {
return (
<div
ref={ref}
data-slot="grid"
className={cn(
"grid",
colsMap[cols],
colsSm && colsSmMap[colsSm],
colsMd && colsMdMap[colsMd],
colsLg && colsLgMap[colsLg],
colsXl && colsXlMap[colsXl],
gapMap[gap],
className,
)}
{...props}
/>
);
},
);
Grid.displayName = "Grid";
const spanMap: Record<Span, string> = {
1: "col-span-1",
2: "col-span-2",
3: "col-span-3",
4: "col-span-4",
5: "col-span-5",
6: "col-span-6",
7: "col-span-7",
8: "col-span-8",
9: "col-span-9",
10: "col-span-10",
11: "col-span-11",
12: "col-span-12",
full: "col-span-full",
auto: "col-auto",
};
const spanSmMap: Record<Span, string> = {
1: "sm:col-span-1",
2: "sm:col-span-2",
3: "sm:col-span-3",
4: "sm:col-span-4",
5: "sm:col-span-5",
6: "sm:col-span-6",
7: "sm:col-span-7",
8: "sm:col-span-8",
9: "sm:col-span-9",
10: "sm:col-span-10",
11: "sm:col-span-11",
12: "sm:col-span-12",
full: "sm:col-span-full",
auto: "sm:col-auto",
};
const spanMdMap: Record<Span, string> = {
1: "md:col-span-1",
2: "md:col-span-2",
3: "md:col-span-3",
4: "md:col-span-4",
5: "md:col-span-5",
6: "md:col-span-6",
7: "md:col-span-7",
8: "md:col-span-8",
9: "md:col-span-9",
10: "md:col-span-10",
11: "md:col-span-11",
12: "md:col-span-12",
full: "md:col-span-full",
auto: "md:col-auto",
};
const spanLgMap: Record<Span, string> = {
1: "lg:col-span-1",
2: "lg:col-span-2",
3: "lg:col-span-3",
4: "lg:col-span-4",
5: "lg:col-span-5",
6: "lg:col-span-6",
7: "lg:col-span-7",
8: "lg:col-span-8",
9: "lg:col-span-9",
10: "lg:col-span-10",
11: "lg:col-span-11",
12: "lg:col-span-12",
full: "lg:col-span-full",
auto: "lg:col-auto",
};
const spanXlMap: Record<Span, string> = {
1: "xl:col-span-1",
2: "xl:col-span-2",
3: "xl:col-span-3",
4: "xl:col-span-4",
5: "xl:col-span-5",
6: "xl:col-span-6",
7: "xl:col-span-7",
8: "xl:col-span-8",
9: "xl:col-span-9",
10: "xl:col-span-10",
11: "xl:col-span-11",
12: "xl:col-span-12",
full: "xl:col-span-full",
auto: "xl:col-auto",
};
export interface GridItemProps extends React.HTMLAttributes<HTMLDivElement> {
span?: Span;
spanSm?: Span;
spanMd?: Span;
spanLg?: Span;
spanXl?: Span;
}
export const GridItem = React.forwardRef<HTMLDivElement, GridItemProps>(
({ className, span, spanSm, spanMd, spanLg, spanXl, ...props }, ref) => {
return (
<div
ref={ref}
data-slot="grid-item"
className={cn(
span && spanMap[span],
spanSm && spanSmMap[spanSm],
spanMd && spanMdMap[spanMd],
spanLg && spanLgMap[spanLg],
spanXl && spanXlMap[spanXl],
className,
)}
{...props}
/>
);
},
);
GridItem.displayName = "GridItem";
Related examples
Fixed columnsAn 8-item, 4-column grid.
Responsive layoutA content + sidebar layout that stacks on mobile.