Grid
A responsive grid layout builder using CSS Grid properties and Tailwind utilities.
Revenue
$12,480
+8.2%
Active users
3,214
+12%
Churn
1.9%
-0.4%
Weekly traffic
Last 12 weeks
1'use client';23import { Grid } from '@/components/ui/grid';45const stats = [6 { label: 'Revenue', value: '$12,480', delta: '+8.2%', up: true },7 { label: 'Active users', value: '3,214', delta: '+12%', up: true },8 { label: 'Churn', value: '1.9%', delta: '-0.4%', up: true },9];1011const sparkline = [35, 55, 40, 70, 50, 85, 60, 95, 75, 100, 80, 90];1213export function Default() {14 return (15 <Grid columns="3" gap="4" className="w-full max-w-lg">16 {stats.map((stat) => (17 <div key={stat.label} className="border-border bg-card rounded-xl border p-4">18 <p className="text-muted-foreground text-xs">{stat.label}</p>19 <p className="mt-1 text-xl font-semibold tracking-tight">{stat.value}</p>20 <p className={`mt-1 text-xs ${stat.up ? 'text-success-text' : 'text-destructive-text'}`}>21 {stat.delta}22 </p>23 </div>24 ))}25 <div className="border-border bg-card col-span-3 rounded-xl border p-4">26 <div className="flex items-baseline justify-between">27 <p className="text-muted-foreground text-xs">Weekly traffic</p>28 <p className="text-muted-foreground text-xs">Last 12 weeks</p>29 </div>30 <div className="mt-3 flex h-16 items-end gap-1.5">31 {sparkline.map((height, index) => (32 <div33 key={index}34 className="bg-primary/70 hover:bg-primary min-w-0 flex-1 rounded-sm transition-colors"35 style={{ height: `${height}%` }}36 />37 ))}38 </div>39 </div>40 </Grid>41 );42}
Installation
Copy and paste the following code into your project.
1import { cva, type VariantProps } from 'class-variance-authority';2import React from 'react';3import { cn } from '../lib/cn';45const gridVariants = cva('grid', {6 variants: {7 columns: {8 '1': 'grid-cols-1',9 '2': 'grid-cols-2',10 '3': 'grid-cols-3',11 '4': 'grid-cols-4',12 '5': 'grid-cols-5',13 '6': 'grid-cols-6',14 '7': 'grid-cols-7',15 '8': 'grid-cols-8',16 '9': 'grid-cols-9',17 '10': 'grid-cols-10',18 '11': 'grid-cols-11',19 '12': 'grid-cols-12',20 none: 'grid-cols-none',21 subgrid: 'grid-cols-subgrid',22 },23 rows: {24 '1': 'grid-rows-1',25 '2': 'grid-rows-2',26 '3': 'grid-rows-3',27 '4': 'grid-rows-4',28 '5': 'grid-rows-5',29 '6': 'grid-rows-6',30 none: 'grid-rows-none',31 subgrid: 'grid-rows-subgrid',32 },33 flow: {34 row: 'grid-flow-row',35 col: 'grid-flow-col',36 dense: 'grid-flow-dense',37 'row-dense': 'grid-flow-row-dense',38 'col-dense': 'grid-flow-col-dense',39 },40 align: {41 start: 'items-start',42 center: 'items-center',43 end: 'items-end',44 baseline: 'items-baseline',45 stretch: 'items-stretch',46 },47 justify: {48 start: 'justify-items-start',49 center: 'justify-items-center',50 end: 'justify-items-end',51 stretch: 'justify-items-stretch',52 },53 gap: {54 '0': 'gap-0',55 '1': 'gap-1',56 '2': 'gap-2',57 '3': 'gap-3',58 '4': 'gap-4',59 '5': 'gap-5',60 '6': 'gap-6',61 '8': 'gap-8',62 '10': 'gap-10',63 '12': 'gap-12',64 '16': 'gap-16',65 '20': 'gap-20',66 '24': 'gap-24',67 '32': 'gap-32',68 },69 gapX: {70 '0': 'gap-x-0',71 '1': 'gap-x-1',72 '2': 'gap-x-2',73 '3': 'gap-x-3',74 '4': 'gap-x-4',75 '5': 'gap-x-5',76 '6': 'gap-x-6',77 '8': 'gap-x-8',78 '10': 'gap-x-10',79 '12': 'gap-x-12',80 '16': 'gap-x-16',81 '20': 'gap-x-20',82 '24': 'gap-x-24',83 '32': 'gap-x-32',84 },85 gapY: {86 '0': 'gap-y-0',87 '1': 'gap-y-1',88 '2': 'gap-y-2',89 '3': 'gap-y-3',90 '4': 'gap-y-4',91 '5': 'gap-y-5',92 '6': 'gap-y-6',93 '8': 'gap-y-8',94 '10': 'gap-y-10',95 '12': 'gap-y-12',96 '16': 'gap-y-16',97 '20': 'gap-y-20',98 '24': 'gap-y-24',99 '32': 'gap-y-32',100 },101 },102 defaultVariants: {103 columns: '1',104 },105});106107const GRID_RESPONSIVE_COLUMNS = {108 sm: {109 '1': 'sm:grid-cols-1',110 '2': 'sm:grid-cols-2',111 '3': 'sm:grid-cols-3',112 '4': 'sm:grid-cols-4',113 '5': 'sm:grid-cols-5',114 '6': 'sm:grid-cols-6',115 '7': 'sm:grid-cols-7',116 '8': 'sm:grid-cols-8',117 '9': 'sm:grid-cols-9',118 '10': 'sm:grid-cols-10',119 '11': 'sm:grid-cols-11',120 '12': 'sm:grid-cols-12',121 none: 'sm:grid-cols-none',122 subgrid: 'sm:grid-cols-subgrid',123 },124 md: {125 '1': 'md:grid-cols-1',126 '2': 'md:grid-cols-2',127 '3': 'md:grid-cols-3',128 '4': 'md:grid-cols-4',129 '5': 'md:grid-cols-5',130 '6': 'md:grid-cols-6',131 '7': 'md:grid-cols-7',132 '8': 'md:grid-cols-8',133 '9': 'md:grid-cols-9',134 '10': 'md:grid-cols-10',135 '11': 'md:grid-cols-11',136 '12': 'md:grid-cols-12',137 none: 'md:grid-cols-none',138 subgrid: 'md:grid-cols-subgrid',139 },140 lg: {141 '1': 'lg:grid-cols-1',142 '2': 'lg:grid-cols-2',143 '3': 'lg:grid-cols-3',144 '4': 'lg:grid-cols-4',145 '5': 'lg:grid-cols-5',146 '6': 'lg:grid-cols-6',147 '7': 'lg:grid-cols-7',148 '8': 'lg:grid-cols-8',149 '9': 'lg:grid-cols-9',150 '10': 'lg:grid-cols-10',151 '11': 'lg:grid-cols-11',152 '12': 'lg:grid-cols-12',153 none: 'lg:grid-cols-none',154 subgrid: 'lg:grid-cols-subgrid',155 },156 xl: {157 '1': 'xl:grid-cols-1',158 '2': 'xl:grid-cols-2',159 '3': 'xl:grid-cols-3',160 '4': 'xl:grid-cols-4',161 '5': 'xl:grid-cols-5',162 '6': 'xl:grid-cols-6',163 '7': 'xl:grid-cols-7',164 '8': 'xl:grid-cols-8',165 '9': 'xl:grid-cols-9',166 '10': 'xl:grid-cols-10',167 '11': 'xl:grid-cols-11',168 '12': 'xl:grid-cols-12',169 none: 'xl:grid-cols-none',170 subgrid: 'xl:grid-cols-subgrid',171 },172} as const;173174type GridColumns = keyof typeof GRID_RESPONSIVE_COLUMNS.sm;175type GridBreakpoint = keyof typeof GRID_RESPONSIVE_COLUMNS;176type GridResponsiveColumns = { base?: GridColumns } & Partial<Record<GridBreakpoint, GridColumns>>;177178type GridOwnProps<E extends React.ElementType> = Omit<179 VariantProps<typeof gridVariants>,180 'columns'181> & {182 as?: E;183 columns?: GridColumns | GridResponsiveColumns | null;184 minChildWidth?: string;185 className?: string;186 style?: React.CSSProperties;187};188189type GridProps<E extends React.ElementType = 'div'> = GridOwnProps<E> &190 Omit<React.ComponentPropsWithRef<E>, keyof GridOwnProps<E>>;191192const BREAKPOINTS: GridBreakpoint[] = ['sm', 'md', 'lg', 'xl'];193194const Grid = <E extends React.ElementType = 'div'>({195 as,196 columns,197 minChildWidth,198 rows,199 flow,200 align,201 justify,202 gap,203 gapX,204 gapY,205 className,206 style,207 ...props208}: GridProps<E>) => {209 const Component: React.ElementType = as ?? 'div';210 const responsive = typeof columns === 'object' && columns !== null ? columns : null;211 const base = typeof columns === 'object' && columns !== null ? columns.base : columns;212213 return (214 <Component215 style={216 minChildWidth217 ? {218 gridTemplateColumns: `repeat(auto-fit, minmax(min(${minChildWidth}, 100%), 1fr))`,219 ...style,220 }221 : style222 }223 className={cn(224 gridVariants({ columns: base, rows, flow, align, justify, gap, gapX, gapY }),225 responsive &&226 BREAKPOINTS.map((breakpoint) => {227 const value = responsive[breakpoint];228 return value ? GRID_RESPONSIVE_COLUMNS[breakpoint][value] : null;229 }),230 className,231 )}232 {...props}233 />234 );235};236237Grid.displayName = 'Grid';238239export { Grid, gridVariants };240export type { GridBreakpoint, GridColumns, GridProps, GridResponsiveColumns };
Update the import paths to match your project structure.
Anatomy
1import { Grid } from '@/components/ui/grid';
1<Grid columns="3" gap="4">2 <div>Column 1</div>3 <div>Column 2</div>4 <div>Column 3</div>5</Grid>
API Reference
Grid
| Prop | Type | Default | Description |
|---|---|---|---|
as | React.ElementType | 'div' | The element type to render the component as |
columns | '1' | ... | '12' | 'none' | 'subgrid' | { base?, sm?, md?, lg?, xl? } | '1' | Number of columns, or one value per breakpoint |
minChildWidth | string | - | Fits as many columns as there is room for, each at least this wide |
rows | '1' | '2' | ... | '6' | 'none' | 'subgrid' | - | Number of rows in the grid |
flow | 'row' | 'col' | 'dense' | 'row-dense' | 'col-dense' | - | Grid layout flows |
align | 'start' | 'center' | 'end' | 'baseline' | 'stretch' | - | Align children along block axis |
justify | 'start' | 'center' | 'end' | 'stretch' | - | Justify children along inline axis |
gap | '0' | '1' | ... | '32' | - | Spacing between items on both axes |
gapX | '0' | '1' | ... | '32' | - | Spacing between columns |
gapY | '0' | '1' | ... | '32' | - | Spacing between rows |
className | string | - | Custom CSS classes |
Found something to improve?
Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.