Grid
Two-dimensional layout with Tailwind — template columns and rows, spanning, auto-placement, named areas, and responsive grids that need no breakpoints.
CSS Grid lays out content in rows and columns at the same time. Use it for page shells, card galleries, dashboards and forms — anywhere items need to line up in both directions.
Overview#
<div class="mx-auto grid max-w-2xl grid-cols-3 gap-4 p-4 text-sm">
<div class="col-span-2 rounded-lg bg-indigo-100 p-4 text-indigo-900">col-span-2</div>
<div class="rounded-lg bg-gray-100 p-4 text-gray-700">1 column</div>
<div class="rounded-lg bg-gray-100 p-4 text-gray-700">1</div>
<div class="rounded-lg bg-gray-100 p-4 text-gray-700">2</div>
<div class="rounded-lg bg-gray-100 p-4 text-gray-700">3</div>
</div>Defining tracks#
Columns#
| Utility | CSS |
|---|---|
grid-cols-1 … grid-cols-12 | repeat(N, minmax(0, 1fr)) |
grid-cols-none | none |
grid-cols-subgrid | subgrid |
grid-cols-[200px_1fr] | Arbitrary track list |
Rows#
| Utility | CSS |
|---|---|
grid-rows-1 … grid-rows-12 | repeat(N, minmax(0, 1fr)) |
grid-rows-[auto_1fr_auto] | Arbitrary track list |
auto-rows-min / auto-rows-max / auto-rows-fr | Size implicitly created rows |
auto-cols-* | Same for implicit columns |
Flow#
| Utility | Behaviour |
|---|---|
grid-flow-row | Default — fill row by row |
grid-flow-col | Fill column by column |
grid-flow-dense | Backfill holes left by spanning items |
Placing items#
| Utility | CSS |
|---|---|
col-span-2 | Span two columns |
col-span-full | Span every column |
col-start-2 / col-end-4 | Explicit lines |
row-span-2, row-start-*, row-end-* | Row equivalents |
col-auto / row-auto | Automatic placement |
<div class="mx-auto grid max-w-xl grid-cols-4 grid-rows-2 gap-2 p-4 text-xs">
<div class="col-span-4 rounded bg-indigo-600 p-3 text-center text-white">col-span-4</div>
<div class="col-span-1 row-span-1 rounded bg-emerald-600 p-3 text-center text-white">1</div>
<div class="col-span-2 rounded bg-emerald-600 p-3 text-center text-white">col-span-2</div>
<div class="col-start-4 rounded bg-amber-500 p-3 text-center text-white">col-start-4</div>
</div>Gaps and alignment#
| Utility | Purpose |
|---|---|
gap-4 / gap-x-4 / gap-y-2 | Space between tracks |
justify-items-* | Align items horizontally within their cell |
items-* | Align items vertically within their cell |
place-items-center | Both at once |
justify-content-* / content-* | Align the whole grid inside the container |
justify-self-* / self-* | Override for one item |
Common patterns#
Responsive card grid#
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">…</div>Auto-fitting grid — no breakpoints at all#
<div class="grid grid-cols-[repeat(auto-fit,minmax(12rem,1fr))] gap-4 p-4 text-sm">
<div class="rounded-lg border border-gray-200 p-4">Card one</div>
<div class="rounded-lg border border-gray-200 p-4">Card two</div>
<div class="rounded-lg border border-gray-200 p-4">Card three</div>
<div class="rounded-lg border border-gray-200 p-4">Card four</div>
</div>Columns are at least 12rem wide and share the remainder. The layout adapts continuously instead of jumping at breakpoints.
Application shell#
<div class="grid min-h-screen grid-rows-[auto_1fr_auto] lg:grid-cols-[16rem_1fr] lg:grid-rows-[auto_1fr]">
<header class="lg:col-span-2 border-b border-gray-200 px-6 py-4">Header</header>
<aside class="hidden border-r border-gray-200 p-4 lg:block">Sidebar</aside>
<main class="p-6">Content</main>
<footer class="border-t border-gray-200 px-6 py-4 lg:col-span-2">Footer</footer>
</div>grid-rows-[auto_1fr_auto] is the canonical sticky-footer grid: header and footer size to content, the middle row takes the rest.
Form layout#
<form class="mx-auto grid max-w-lg grid-cols-1 gap-4 p-4 sm:grid-cols-2">
<label class="block text-sm">
<span class="mb-1 block font-medium text-gray-700">First name</span>
<input class="w-full rounded-md border border-gray-300 px-3 py-2" />
</label>
<label class="block text-sm">
<span class="mb-1 block font-medium text-gray-700">Last name</span>
<input class="w-full rounded-md border border-gray-300 px-3 py-2" />
</label>
<label class="block text-sm sm:col-span-2">
<span class="mb-1 block font-medium text-gray-700">Email</span>
<input type="email" class="w-full rounded-md border border-gray-300 px-3 py-2" />
</label>
</form>sm:col-span-2 is how you make one field full-width in a two-column form.
Named areas#
Tailwind has no grid-areas utilities, so use an arbitrary property:
<div class="grid gap-4 [grid-template-areas:'header_header''sidebar_main'] grid-cols-[12rem_1fr]">
<header class="[grid-area:header]">…</header>
<aside class="[grid-area:sidebar]">…</aside>
<main class="[grid-area:main]">…</main>
</div>Underscores stand in for spaces inside arbitrary values.
Subgrid#
Align nested content to the outer grid's tracks:
<div class="grid grid-cols-3 gap-4">
<div class="col-span-3 grid grid-cols-subgrid gap-4">
<div class="col-start-2">Aligned to the parent's second column</div>
</div>
</div>Overlapping elements#
Place two items in the same cell to layer them without absolute positioning:
<div class="grid">
<img class="col-start-1 row-start-1 h-48 w-full object-cover" src="…" alt="">
<div class="col-start-1 row-start-1 bg-gradient-to-t from-black/60 to-transparent"></div>
</div>Grid vs Flexbox#
| Question | Answer |
|---|---|
| Do rows and columns need to align? | Grid |
| Is it a single row or column of things? | Flexbox |
| Should item size be driven by content? | Flexbox |
| Should item size be driven by the layout? | Grid |
| Do you need to overlap items? | Grid |
| Is the number of items unknown and variable? | Either — auto-fit grid or flex-wrap |
They compose: a grid cell is very often a flex container.
Common mistakes#
gapon the item instead of the container.gap-*belongs on the element withgrid.- Forgetting
grid.grid-cols-3alone does nothing withoutdisplay: grid. - Using
grid-cols-*whenauto-fitwould do. Four breakpoint variants often replace one arbitrary track list. - Fixed
grid-rows-*with variable content. Explicit row counts clip or stretch awkwardly; preferauto-rows-*. - Expecting
col-span-2to work in a one-column grid. It clamps to the available tracks. - Nesting a grid where a flex row would do. Grid inside grid inside grid gets hard to reason about quickly.
Related topics#
- Flexbox — one-dimensional layout
- Container & Columns — page width and multi-column text
- Responsive Design — breakpoint variants
- Arbitrary Values — custom track lists