Ask a room of developers how they built their first multi-column layout and you will hear stories about floats, clearfixes and a fair amount of swearing. CSS Grid took most of that pain away, but it can still look like a large subject from the outside. It isn't. A handful of properties will carry you through the vast majority of page layouts you will ever build, and the rest can wait until you actually need it.
What follows is a walk through those properties in the order you will use them: a basic grid, fluid columns that adapt on their own, spacing and alignment, and a dashboard layout you can lift straight into a project.
What Grid Actually Does
CSS Grid is a two-dimensional layout system. You declare a container as a grid, describe its columns and rows, and the browser places the children into the resulting cells. That is the whole idea, and everything else is detail built on top of it.
The usual comparison is with Flexbox, and it is a useful one. Flexbox arranges items along a single axis — a row of buttons, a stack of cards, a navigation bar. Grid handles both axes at once, which makes it the natural choice for page structure. In practice you will mix them: Grid for the skeleton of a page, Flexbox inside the components that sit in it.
One thing worth knowing early: grid items behave like normal elements. No floats, no clearing, no collapsing parent heights, and no dependence on source order for positioning. If you have ever cursed a float-based layout at midnight, that last point alone justifies the switch.
Your First Grid: Columns, fr Units and repeat()
Start with a container and a column definition. This gives you a fixed sidebar, a flexible main column and a narrower aside:
.layout { display: grid; grid-template-columns: 200px 1fr 300px; gap: 1rem; }
The fr unit means "one fraction of the space that is left over". The browser subtracts the fixed widths and the gaps, then divides what remains between the flexible tracks. Change 1fr to 2fr and that column takes twice as much as its neighbour.
Writing 1fr 1fr 1fr gets tedious, so use repeat(3, 1fr) instead. You can mix and match: repeat(2, 1fr) 200px is perfectly valid.
Rows work the same way, but you will often leave them to the browser. Items that do not fit on one row flow onto the next, and their height is determined by their content. When you want a guaranteed minimum height that still grows with longer text, set grid-auto-rows: minmax(120px, auto) on the container.
Responsive by Default: minmax() and auto-fit
This is the line that saves the most work in real projects:
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
Read it as: fit in as many columns as possible, each at least 240 pixels wide, and let them divide the remaining space equally. On a wide screen you get four cards across; narrow the window and you get three, then two, then one. No media queries, no breakpoints to maintain.
There is a subtle difference between auto-fit and auto-fill. With auto-fill, empty tracks are kept, so a row of two cards on a wide screen stays at the left and keeps its narrow width. With auto-fit, those empty tracks collapse and the visible cards stretch to fill the row. For card grids, auto-fit is usually what you want.
One guard rail: on very narrow screens, 240 pixels plus padding can overflow. Wrapping the minimum, as in minmax(min(240px, 100%), 1fr), keeps it safe.
Building a Dashboard with Named Areas
Once a layout has more than a couple of regions, position-by-numbers gets confusing. Named areas let you draw the layout as text:
.dashboard {
display: grid;
grid-template-columns: 220px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"sidebar header"
"sidebar main"
"sidebar footer";
gap: 1rem;
min-height: 100vh;
}
Then assign each child with a single line: .sidebar { grid-area: sidebar; }, .header { grid-area: header; }, and so on. Each word is a cell, each row of the string is a row of the grid, and a full stop creates an empty cell.
The payoff comes when you resize. A short media query rewrites the map and the whole structure changes:
@media (max-width: 48em) { .dashboard { grid-template-columns: 1fr; grid-template-areas: "header" "main" "sidebar" "footer"; } }
Everything else stays put. No absolute positioning, no JavaScript, no duplicated markup.
Spacing, Alignment and Rules of Thumb
Spacing is handled by gap, which applies between tracks and never to the outer edges of the container — so no more :last-child margin resets. Use column-gap and row-gap separately when the horizontal and vertical rhythm should differ.
For alignment, the same pair of properties appears at two levels. On the container, align-items and justify-items set the default for every child; on a single child, align-self and justify-self override it. The shorthand place-items: center is the quickest way to centre something inside a cell.
A few habits worth forming:
- Reach for Grid when the layout has structure in both directions, and Flexbox for a single row or column inside it.
- Prefer gap over margins on grid items; margins fight with the layout and break the spacing rhythm.
- Use grid-column: 1 / -1 to make an item span the full width of its container without counting columns by hand.
- Keep rows content-sized with auto or minmax() rather than fixed heights.
- Remember that visual placement does not change tab order — a sidebar moved with grid-area is still read where it appears in the HTML.
Mistakes That Trip People Up
Three come up again and again. The first is fighting the layout with percentages and calc() when fr does the job in a single value. The second is reordering content with grid areas and accidentally sending keyboard users on a confusing journey around the page; if the visual order matters, change the source order too.
The third is subtler: a 1fr track has an automatic minimum size based on its content, so one long unbreakable word or a large image can push a column wider than you intended. Setting minmax(0, 1fr) on the track, or min-width: 0 on the item, removes the problem.
Try It on a Real Page
Pick a page you have already built, even a small one with a header, some cards and a footer. Write the HTML first, in the order it should be read. Add display: grid to the wrapper and one column definition. Then resize the browser window slowly from narrow to wide and watch what happens.
Test at roughly 320px, 768px and 1280px, click through the page with the keyboard to confirm the tab order still makes sense, and check the layout with a long headline and a missing image. When something breaks, open DevTools and switch on the grid overlay — Firefox and Chrome both draw the tracks and line names directly on the page, which turns most guesswork into a two-second inspection.
Do that once and the properties stop feeling like an API to memorise. Subgrid, when a nested grid needs to line up with its parent's tracks, is supported in all current major browsers and is worth reading about next — but only after the basics above feel like second nature.
Photo: Vitaly Gariev / Pexels



