CSS Introduction to CSS Grid
CSS Grid Layout is a two-dimensional layout system built into CSS. Unlike Flexbox, which lays items out along a single row or column, Grid lets you control rows and columns at the same time, making it the natural tool for page layouts, image galleries, dashboards, and any design that needs precise placement in two dimensions. Once you understand Grid, entire categories of layout problems that used to require floats, absolute positioning, or fragile hacks become a handful of declarative property values.
This lesson introduces the mental model behind Grid, walks through its core syntax, and builds up from a simple three-column layout to a responsive gallery that adapts to any screen size without a single media query.
Overview / How Grid Works
To use Grid, you set display: grid on a container element. That element becomes a grid container, and its direct children automatically become grid items — you do not need to add any classes or wrapper markup to the children themselves. The browser’s layout engine then does something fundamentally different from normal block flow: instead of stacking children top to bottom, it builds an invisible grid of rows and columns based on the properties you set on the container, and places each item into a cell (or a span of cells) on that grid.
The grid is made of a few structural concepts you’ll see throughout this lesson:
- Grid lines — the numbered dividing lines that run horizontally and vertically between rows and columns. A grid with 3 columns has 4 vertical lines (1 through 4).
- Grid tracks — the space between two adjacent grid lines; this is a row or a column.
- Grid cells — the smallest unit, formed by the intersection of one row track and one column track.
- Grid areas — a rectangular group of one or more cells, which an item can be placed into.
Grid layout happens in two phases inside the rendering engine. First, the engine computes the size of every column and row track based on your grid-template-columns/grid-template-rows values, resolving fixed lengths, percentages, and flexible fr units against the available space in the container’s content box (the same content box used by the standard box model). Second, it places each grid item into a cell or area, either automatically (following the item’s source order into the next open cell) or explicitly, if you’ve assigned it a specific line or named area. This two-phase process is why Grid is so predictable: sizing is resolved before placement, so items never have to “guess” how much room they have.
Because Grid is a layout mode of its own, a grid container’s children ignore some properties that matter in normal flow (like float and vertical-align), and gain access to entirely new ones, like grid-column and grid-row, that only make sense inside a grid.
Syntax
The general shape of a grid layout is: declare the grid on the parent, then optionally place children within it.
.container {
display: grid;
grid-template-columns: track-list;
grid-template-rows: track-list;
gap: row-gap column-gap;
}
.item {
grid-column: start-line / end-line;
grid-row: start-line / end-line;
}
| Property | Applies to | Purpose |
|---|---|---|
display: grid |
Container | Turns the element into a grid container (use inline-grid for an inline-level container). |
grid-template-columns |
Container | Defines the number and size of column tracks. |
grid-template-rows |
Container | Defines the number and size of row tracks. |
gap (row-gap/column-gap) |
Container | Space between tracks, without adding space at the outer edges. |
grid-template-areas |
Container | Names rectangular regions of the grid using a text-based diagram. |
grid-column / grid-row |
Item | Places an item at specific line numbers, or makes it span multiple tracks. |
grid-area |
Item | Assigns an item to a named area from grid-template-areas. |
repeat() |
Track list value | Repeats a track pattern without retyping it. |
minmax() |
Track list value | Gives a track a minimum and maximum size. |
fr unit |
Track list value | A fraction of the leftover space in the grid container, after fixed-size tracks are subtracted. |
Examples
Example 1: A basic fixed grid
Applied to a container <div class="container"> holding six <div class="item"> children:
.container {
display: grid;
grid-template-columns: 200px 200px 200px;
grid-template-rows: 100px 100px;
gap: 20px;
}
.item {
background-color: #4a90d9;
border-radius: 6px;
}
Result: The six items arrange themselves into a neat 3-column, 2-row grid: three 200px-wide columns and two 100px-tall rows, each cell separated by a 20px gap on all sides between tracks. Because there are exactly six items and six cells, every cell is filled with no gaps or overflow.
Notice that no floats, no display: inline-block, and no manual widths on the children were needed — the container’s grid-template-columns and grid-template-rows values alone define the entire structure, and children are placed into it automatically in source order.
Example 2: A named-areas page layout
.page {
display: grid;
grid-template-columns: 200px 1fr 200px;
grid-template-rows: 80px 1fr 60px;
grid-template-areas:
"header header header"
"sidebar content aside"
"footer footer footer";
min-height: 100vh;
gap: 16px;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }
Result: A classic “holy grail” layout appears: a full-width header bar 80px tall, a footer 60px tall, and a middle row that fills the remaining viewport height, split into a 200px sidebar, a flexible center content column, and a 200px aside — all with 16px of breathing room between every region.
The grid-template-areas value is a visual ASCII diagram: each quoted string is one row, each word is one column’s area name, and repeating a name across cells (like header three times) makes that item span all of them. Children are then matched to a region purely by name via grid-area, completely decoupling the visual arrangement from the order the elements appear in the HTML.
Example 3: A responsive, auto-wrapping gallery
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: clamp(0.5rem, 2vw, 1.5rem);
}
.gallery img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 8px;
}
Result: Images lay out in as many columns as fit the container, with each column at least 220px wide and growing to share any leftover space equally. On a narrow phone screen this collapses to a single column; on a wide desktop it might show five or six columns — all without a single @media query, and the gap between images fluidly scales between 0.5rem and 1.5rem depending on viewport width.
This pattern — repeat(auto-fit, minmax(min, 1fr)) — is one of the most useful idioms in modern CSS: it asks the browser to fit as many tracks as possible at the minimum size, then stretch them to fill any remaining space.
How It Works Step by Step (Under the Hood)
When the rendering engine encounters a grid container, it performs layout in roughly this order:
- 1. Build the explicit grid. It reads
grid-template-columnsandgrid-template-rowsand creates that many column and row tracks, numbering the lines between them starting at 1. - 2. Resolve track sizes. Fixed units (
px,%,rem) are resolved first against the container’s content box. Then allfrtracks divide up whatever space is left: for example, in a container 900px wide with columns200px 1fr 1fr, 200px is subtracted first, leaving 700px split evenly into two 350pxfrtracks. - 3. Place items. Items with an explicit
grid-column/grid-row/grid-areago exactly where specified. Remaining items are placed automatically: the engine walks the grid in source order (row by row, by default) and drops each item into the next open cell, creating extra implicit rows viagrid-auto-rowsif it runs out of explicitly defined ones. - 4. Apply gaps and alignment.
gapinserts fixed space between tracks (not at the outer edge), and properties likejustify-items/align-itemsposition each item’s content box within its cell if the item is smaller than the cell. - 5. Paint. Once every item has a final box size and position, the engine paints backgrounds, borders, and content exactly as it would for any other box — Grid only changes where the box goes, not how the box model itself works.
Because sizing (step 2) always completes before placement (step 3), Grid can do things Flexbox structurally cannot, like aligning items across both rows and columns simultaneously, or leaving a deliberate empty cell.
Common Mistakes
Mistake 1: Setting grid-column/grid-row without display: grid
grid-column and grid-area only have an effect on children of a grid container. If you forget display: grid on the parent, these properties are simply ignored and the layout falls back to normal block flow.
.container {
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.item {
grid-column: span 2;
}
Nothing above actually creates a grid, because display: grid was never set — .container stays a plain block element and .item‘s grid-column: span 2 does nothing. The fix is to always pair template properties with display: grid:
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
}
.item {
grid-column: span 2;
}
Mistake 2: Passing three arguments to minmax()
minmax() always takes exactly two arguments: a minimum and a maximum. A common typo is adding a third value expecting some kind of “ideal” size:
.gallery {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr, 300px));
}
This is invalid and the whole grid-template-columns declaration is dropped by the browser. The corrected version keeps it to a min and a max:
.gallery {
grid-template-columns: repeat(auto-fit, minmax(200px, 300px));
}
Best Practices
- Reach for Grid when you need control in two dimensions (rows and columns together); reach for Flexbox when you only need to align items along one axis.
- Prefer
frunits over percentages for flexible tracks — they account forgapautomatically, while percentages do not. - Use
grid-template-areasfor page-level layouts; the ASCII-art diagram is self-documenting and makes the HTML/CSS relationship easy to read at a glance. - Use
repeat(auto-fit, minmax(min, 1fr))for responsive card/image grids instead of writing multiple@mediabreakpoints. - Use
gapinstead of margins on grid items to space out tracks — it never adds unwanted space at the container’s outer edge. - Name your grid lines or areas in complex layouts (
[sidebar-start],grid-area: header) rather than relying on numeric line positions, which get harder to track as a layout grows.
Practice Exercises
- Exercise 1: Build a 4-column, 3-row grid of 12 boxes with a 10px gap, using
repeat()instead of writing each track individually. - Exercise 2: Create a two-region layout with a fixed 250px sidebar and a flexible main content area that fills the rest of the width, using
grid-template-columnsand thefrunit. - Exercise 3: Take the responsive gallery pattern from Example 3 and modify it so each column is never narrower than 150px and never wider than 250px, using
minmax().
Summary
- CSS Grid is a two-dimensional layout system: set
display: gridon a container to control its children’s rows and columns together. grid-template-columnsandgrid-template-rowsdefine track sizes; thefrunit distributes leftover space proportionally.grid-template-areaslets you lay out named regions visually, then assign children to them withgrid-area.repeat(auto-fit, minmax(min, 1fr))is the standard idiom for responsive grids without media queries.- Track sizing is always resolved before item placement, which is what makes Grid layouts precise and predictable.
grid-column/grid-row/grid-areaonly work on children of an actual grid container — forgettingdisplay: gridsilently disables them.
