For a long time, building two-dimensional layouts in CSS meant fighting the tools. Floats, clearfixes, and table hacks all existed because there was no proper way to arrange content in rows and columns at the same time. CSS Grid changed that. It is a layout system built specifically for two dimensions, giving you direct control over both the horizontal and vertical structure of a page. Once you understand how the container and the items relate, most layouts that used to require workarounds become straightforward.
Turning On the Grid
A grid starts with a container. Setting display: grid on an element turns it into a grid container, and every direct child becomes a grid item automatically:
.container { display: grid;}
From here, the work splits into two halves. The container controls the overall structure, how many rows and columns exist and how big they are. The items control where individual pieces sit within that structure. Most of CSS Grid is just these two sets of properties working together.
Defining Rows and Columns
The foundation of any grid is its tracks, the columns and rows that form the structure. You define them with grid-template-columns and grid-template-rows:
.container { display: grid; grid-template-columns: 100px 200px auto; grid-template-rows: 50px auto 100px;}
This creates three columns and three rows. The first column is a fixed 100px, the second a fixed 200px, and the third uses auto, meaning it expands to fill whatever space remains. The rows follow the same logic: a fixed first row, an expanding middle row, and a fixed last row. Mixing fixed sizes with auto is common when you want some tracks to stay constant while others absorb the leftover space.
The Fractional Unit
Fixed pixel values are useful, but the fractional unit fr is what makes grids genuinely flexible. It distributes available space proportionally rather than in absolute terms:
.container { grid-template-columns: 1fr 2fr 1fr;}
Here the middle column takes twice as much space as each of the outer columns, and the whole thing scales fluidly with the container’s width. When you want equal columns, the repeat() function saves you from typing the same value over and over:
.container { grid-template-columns: repeat(3, 1fr);}
This produces three equal columns. repeat() is purely a convenience, but with larger grids it keeps your declarations readable.
Handling Tracks You Did Not Explicitly Define
Sometimes more items appear than your defined grid has room for. When that happens, Grid creates new tracks automatically, called the implicit grid. You control the size of these auto-generated tracks with grid-auto-rows and grid-auto-columns:
.container { grid-auto-rows: 100px;}
Now any row Grid has to create beyond your explicit definition will be 100px tall. This matters for content of unknown length, like a list of cards pulled from a database, where you do not know in advance how many rows you will need.
Spacing Between Items
The gap property adds space between grid tracks without you having to manage margins on individual items:
.container { gap: 20px;}
This puts 20px between every row and column. When you want different spacing in each direction, split it into the two underlying properties:
.container { row-gap: 20px; column-gap: 10px;}
Using gap rather than margins is cleaner because the spacing applies only between items, never around the outer edge of the grid, so you do not get unwanted padding at the boundaries.
Two Kinds of Alignment
Alignment in Grid is the part that confuses people most, because there are two distinct things you might want to align, and they use similarly named properties.
The first is aligning the items within their cells. justify-items controls horizontal alignment inside each cell, and align-itemscontrols vertical alignment:
.container { justify-items: center; align-items: center;}
Both accept start, center, end, and stretch, with stretch being the default that makes items fill their cells.
The second is aligning the entire grid within the container, which only matters when the grid is smaller than the container holding it. That is the job of justify-content and align-content:
.container { justify-content: center; align-content: center;}
These accept the same positional values plus distribution options like space-between and space-around, which spread the tracks out with space between or around them.
The distinction worth remembering is items versus content. The items properties position things inside their cells. The content properties position the whole grid inside the container.
If you want to centre items both ways at once, place-items is a shorthand for the two item properties together:
.container { place-items: center;}
Positioning Individual Items
So far everything has been about the container. Items can also take control of their own placement. The grid-column and grid-row properties let an item span across multiple tracks by specifying its start and end lines:
.feature { grid-column: 1 / 3; grid-row: 2 / 4;}
The numbers refer to grid lines, not tracks, which is a subtle but important point. 1 / 3 means “start at line 1 and end at line 3,” which spans the two columns between those lines. When you only care about how many tracks to cover rather than exact positions, the span keyword is simpler:
.feature { grid-column: span 2;}
This makes the item cover two columns wherever it happens to land.
Naming Areas for Readable Layouts
For page-level structure, line numbers can become hard to follow. Grid offers a more visual alternative with named template areas. You draw the layout as a set of strings on the container, then assign each item to a named region:
.container { display: grid; grid-template-areas: "header header" "sidebar content" "footer footer";}.header { grid-area: header; }.sidebar { grid-area: sidebar; }.content { grid-area: content; }.footer { grid-area: footer; }
The template literally maps out the layout in text. The header spans both columns of the top row, the sidebar and content sit side by side in the middle, and the footer spans both columns at the bottom. This is one of Grid’s most readable features, because the CSS visually resembles the layout it produces, making it easy to rearrange a whole page by editing a few lines.
A Complete Example
Putting the core pieces together, here is a simple six-item grid:
.container { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(2, 100px); gap: 10px;}.item { background: lightblue; display: flex; align-items: center; justify-content: center;}
<div class="container"> <div class="item">1</div> <div class="item">2</div> <div class="item">3</div> <div class="item">4</div> <div class="item">5</div> <div class="item">6</div></div>
This produces a three-by-two grid of equal cells with 10px spacing, each item centring its own content. Notice that the items use Flexbox internally to centre their numbers, which highlights an important point: Grid and Flexbox are not rivals. They work together, with Grid handling the outer two-dimensional structure and Flexbox handling alignment within individual cells.
When to Use Grid, and When Not To
Grid is the right tool when you are working in two dimensions at once: full-page layouts, complex structures with both rows and columns to manage, or any situation where you need to align elements across both axes. The named-areas feature in particular makes it excellent for overall page scaffolding.
It is less suited to simple one-dimensional arrangements. If you are laying out a single row of buttons or a navigation bar where everything flows in one direction, Flexbox is usually the cleaner choice. A good rule of thumb: reach for Grid when you are thinking about rows and columns together, and reach for Flexbox when you are thinking about a single line of content.
Property Reference
| Property | What it does |
|---|---|
display: grid | Enables grid layout on the container |
grid-template-columns | Defines the number and size of columns |
grid-template-rows | Defines the number and size of rows |
grid-template-areas | Assigns names to regions of the grid |
grid-auto-rows | Sets the height of implicitly created rows |
grid-auto-columns | Sets the width of implicitly created columns |
gap | Sets spacing between grid tracks |
justify-items | Aligns items horizontally inside their cells |
align-items | Aligns items vertically inside their cells |
justify-content | Aligns the whole grid horizontally in the container |
align-content | Aligns the whole grid vertically in the container |
grid-column | Positions an item across columns |
grid-row | Positions an item across rows |
order | Reorders items, the same way it works in Flexbox |
See you soon
[…] Gridbox: https://datalad.co.uk/a-practical-guide-to-css-grid/ […]
[…] page layouts where you need to control rows and columns at the same time. That is what CSS Grid is for. The simplest way to decide: if you are thinking about a single line of content, whether a […]
[…] bars, rows of cards, and one-dimensional arrangements. Setting display: grid makes it a grid container, arranging children in two dimensions across rows and columns, which suits dashboards and full-page […]
[…] CSS positioning gives you five ways to place an element. static follows the normal flow, relative shifts an element while keeping its space and anchoring its children, absolute detaches an element and places it against a positioned ancestor, fixedpins it to the viewport, and sticky switches from relative to fixed as you scroll. Combine these with the offset properties to control placement and z-index to control overlap, and you have the full toolkit for arranging elements exactly where you want them. The single most common pattern to remember is a relative parent with an absolute child, because once that clicks, a huge amount of layout work falls into place. […]