Grid

Component for creating bidimensional grid layouts.

Usage

GridExample.tsx
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
<Grid cols="12" rows="3" gapX="6" gapY="6" className="bg-muted-200 mt-4 h-96 rounded-md p-4">
<div className="bg-muted-600 col-span-12 flex h-full w-full items-center justify-center rounded-sm text-white">Header</div>
<div className="bg-muted-600 col-span-3 flex h-full w-full items-center justify-center rounded-sm text-white">Sidebar</div>
<div className="bg-muted-600 col-span-9 flex h-full w-full items-center justify-center rounded-sm text-white">Content</div>
<div className="bg-muted-600 col-span-12 flex h-full w-full items-center justify-center rounded-sm text-white">Footer</div>
</Grid>

Here's the <Grid /> component in action.

Header
Sidebar
Content
Footer

Features

  • Inherits the fluid space scale on the gapX and gapY props.
  • Removes inaccessible CSS features.

Parts and their API

Grid

Component for creating bidimensional grid layouts.

Props

PropTypeDefaultDescription
inlinebooleanfalseWhether the grid is inline or not.
cols"number"1The number of columns.
rows"number"1The number of rows.
justify'start' | 'center' | 'end' | 'between''start'The horizontal alignment of the grid.
align'start' | 'center' | 'end' | 'stretch''start'The vertical alignment of the grid.
gapX0 to 200The horizontal gap between the grid items.
gapY0 to 200The vertical gap between the grid items.

Notes

If you set inline, justify has no meaning because the <Grid /> has the least width possible.

Setting align, only has meaning if the <Grid /> has a height. Otherwise it will have the least height possible.

If you set justify="stretch", the children shouldn't have a fixed width. Otherwise they won't stretch.

If you set align="stretch", the children shouldn't have a fixed height. Otherwise they won't stretch.

Setting justify="stretch" has the same effect as setting w-full on all the children (assuming flow="row").

Setting align="stretch" has the same effect as setting h-full on all the children (assuming flow="row").

Accessibility

Changing visual order creates a disconnect between content and presentation and is, therefore, bad for accessibility. For this reason, I've ommitted grid-auto-flow:row dense and grid-auto-flow: column dense.

Source

grid.tsx
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  53. 53
  54. 54
  55. 55
  56. 56
  57. 57
  58. 58
  59. 59
  60. 60
  61. 61
  62. 62
  63. 63
  64. 64
  65. 65
  66. 66
  67. 67
  68. 68
  69. 69
  70. 70
  71. 71
  72. 72
  73. 73
  74. 74
  75. 75
  76. 76
  77. 77
  78. 78
  79. 79
  80. 80
  81. 81
  82. 82
  83. 83
  84. 84
  85. 85
  86. 86
  87. 87
  88. 88
  89. 89
  90. 90
  91. 91
  92. 92
  93. 93
  94. 94
  95. 95
  96. 96
  97. 97
  98. 98
  99. 99
  100. 100
  101. 101
  102. 102
  103. 103
  104. 104
  105. 105
  106. 106
  107. 107
  108. 108
  109. 109
  110. 110
  111. 111
  112. 112
  113. 113
  114. 114
  115. 115
  116. 116
  117. 117
  118. 118
  119. 119
  120. 120
import { type VariantProps, cva } from 'class-variance-authority'
import { type HTMLAttributes, type PropsWithChildren, forwardRef } from 'react'
import { cn } from '#app/utils/tailwind-merge.ts'
const gridVariants = cva('grid', {
variants: {
flow: {
row: 'grid-flow-row',
col: 'grid-flow-col',
},
justify: {
start: 'justify-items-start',
center: 'justify-items-center',
end: 'justify-items-end',
stretch: 'justify-items-stretch',
},
align: {
start: 'items-start',
center: 'items-center',
end: 'items-end',
baseline: 'items-baseline',
stretch: 'items-stretch',
},
gapX: {
'0': 'gap-x-fluid-0',
'1': 'gap-x-fluid-1',
'2': 'gap-x-fluid-2',
'3': 'gap-x-fluid-3',
'4': 'gap-x-fluid-4',
'5': 'gap-x-fluid-5',
'6': 'gap-x-fluid-6',
'7': 'gap-x-fluid-7',
'8': 'gap-x-fluid-8',
'9': 'gap-x-fluid-9',
'10': 'gap-x-fluid-10',
'11': 'gap-x-fluid-11',
'12': 'gap-x-fluid-12',
'13': 'gap-x-fluid-13',
'14': 'gap-x-fluid-14',
'15': 'gap-x-fluid-15',
'16': 'gap-x-fluid-16',
'17': 'gap-x-fluid-17',
'18': 'gap-x-fluid-18',
'19': 'gap-x-fluid-19',
'20': 'gap-x-fluid-20',
},
gapY: {
'0': 'gap-y-fluid-0',
'1': 'gap-y-fluid-1',
'2': 'gap-y-fluid-2',
'3': 'gap-y-fluid-3',
'4': 'gap-y-fluid-4',
'5': 'gap-y-fluid-5',
'6': 'gap-y-fluid-6',
'7': 'gap-y-fluid-7',
'8': 'gap-y-fluid-8',
'9': 'gap-y-fluid-9',
'10': 'gap-y-fluid-10',
'11': 'gap-y-fluid-11',
'12': 'gap-y-fluid-12',
'13': 'gap-y-fluid-13',
'14': 'gap-y-fluid-14',
'15': 'gap-y-fluid-15',
'16': 'gap-y-fluid-16',
'17': 'gap-y-fluid-17',
'18': 'gap-y-fluid-18',
'19': 'gap-y-fluid-19',
'20': 'gap-y-fluid-20',
},
},
})
type GridProps = PropsWithChildren<HTMLAttributes<HTMLDivElement>> &
VariantProps<typeof gridVariants> & {
inline?: boolean
cols?: `${number}` // Any string which could be produced by coercing a number
rows?: `${number}` // Any string which could be produced by coercing a number
}
/**
* Grid component for creating bidimensional layouts.
*
* @component
* @example
* ```jsx
* <Grid cols="12" rows="3" gapX="6" gapY="6" className="bg-muted-200 mt-4 h-96 rounded-md p-4">
* <div className="bg-muted-600 col-span-12 flex h-full w-full items-center justify-center rounded-sm text-white">Header</div>
* <div className="bg-muted-600 col-span-3 flex h-full w-full items-center justify-center rounded-sm text-white">Sidebar</div>
* <div className="bg-muted-600 col-span-9 flex h-full w-full items-center justify-center rounded-sm text-white">Content</div>
* <div className="bg-muted-600 col-span-12 flex h-full w-full items-center justify-center rounded-sm text-white">Footer</div>
* </Grid>
* ```
*
* @param {`{number}`} [cols] - The number of columns
* @param {`{number}`} [rows] - The number of rows
* @param {string} [justify='start'] - The alignment of items along the main axis. Can be 'start', 'center', 'end', or 'stretch'.
* @param {string} [align='start'] - The alignment of items along the cross axis. Can be 'start', 'center', 'end', 'baseline', or 'stretch'.
* @param {boolean} [gapX='0'] - The gap between items along the main axis. Can be a number from 0 to 20.
* @param {boolean} [gapY='0'] - The gap between items along the cross axis. Can be a number from 0 to 20.
* @param {string} [className] - Additional CSS class names.
* @param {React.HTMLAttributes<HTMLDivElement>} [props] - Additional HTML attributes.
* @returns {JSX.Element} The Grid component.
*/
const Grid = forwardRef<HTMLDivElement, GridProps>(({ inline = false, cols, rows, flow = 'row', justify = 'start', align = 'start', gapX = '0', gapY = '0', className, ...props }, ref) => {
return (
<div
ref={ref}
className={cn(gridVariants({ flow, justify, align, gapX, gapY }), inline ? `inline-grid` : ``, className)}
style={{
gridTemplateColumns: cols ? `repeat(${cols}, minmax(0, 1fr))` : undefined,
gridTemplateRows: rows ? `repeat(${rows}, minmax(0, 1fr))` : undefined,
}}
{...props}
/>
)
})
Grid.displayName = 'Grid'
export { Grid }