Vegibit - Web Development Tutorials

A practical corner for shipping real web stuff: layouts that behave, APIs that don’t bite, and tiny “why is this broken” moments turned into repeatable fixes.

CSS Grid is a major paradigm shift for how we construct layouts on the web. But seriously, most tutorials out there make it far more intimidating than it should be. You don't need to memorize all of the properties and fully grok the spec before you start using it; just take a few key concepts, jam 'em into a website, and get more as needed. The central idea is that Grid lets you build two-dimensional layouts (rows AND columns at the same time), as opposed to flexbox, which is really only one-dimensional (either rows or columns, but not both).

Anyway, first of all, you need a grid container. Set display: grid on any parent element, and you're done. That parent now controls the layout of its immediate children, which become called grid items. The existing markup doesn't change (no extra classes or wrapping divs or other markup gymnastics); the parent element, by virtue of having display: grid set on it, automatically becomes a grid container and all of its direct children become grid items.

After that you specify how many columns or rows you want with grid-template-columns and grid-template-rows. For example, grid-template-columns: 1fr 1fr 1fr; the "fr" unit is a fractional unit and it means that the space should be split equally (each column takes 1 out of 3 available fractions). You can also mix units… it's perfectly valid to say 200px 1fr 2fr and have the first column take a fixed 200px width, and the other two columns split the remaining space 1:2 between them.

The gap property is kind of underrated but really good because it makes adding spacing between items easy. gap: 20px and you now have 20px gutter between all of your items, with no more collapsing margins or other edge-cases to deal with. You can even do gap: 20px 10px if you want different spacing between columns and rows.

This is where things start to get more interesting (and kind of powerful). You can use grid-column and grid-row to explicitly place your items. If you want an item to start on column line 1 and end on column line 3, that's grid-column: 1 / 3. Or maybe you want an item to span 2 columns wherever it would naturally land: grid-column: span 2. This opens up all kinds of design layouts that you really can't easily achieve with flexbox, like asymmetrical layouts where images overlap or consume non-uniform amounts of space. That was the kind of design trend that a lot of sites followed for a while, magazine-style design. Flexbox requires all sorts of hacks to make that happen; Grid makes it easy.

Grid areas is another one of those things that sounds more complicated than it needs to be. With grid-template-areas, you can actually draw your layout using text. Define your sections (header, sidebar, content, footer, whatever) and then sketch it out in lines like "header header header" then "sidebar content content" then "footer footer footer", where each line represents a row and each word is a column. Then just use grid-area: header on an item and the browser will place it there for you. It feels almost too easy. The way we build intuition for CSS by imagining how the browser reads the markup feels wrong when it's so trivial to visualize the actual layout directly in your CSS, but grid-template-areas is one of those things that's really great for prototyping or helping non-technical people understand how the layout works. Here's what it looks like in use:

The thing people get hung up on is whether to use auto-fit or auto-fill when repeating. Honestly just pick one and experiment with it. Both of them are repeat values that let you create responsive grids without media queries. repeat(auto-fit, minmax(250px, 1fr)) will create as many columns as can fit in the container while still having at least 250px width. The difference between the two is that auto-fit will collapse empty tracks and auto-fill will not… in most use-cases you probably want auto-fit unless you're doing some weird thing with explicit placement.

(and, yes, literally “move to next line”) and then assign elements to those named areas with grid-area: header; . It looks weird in the code, but it’s very readable. You can look at the CSS and see the structure of the layout. It’s especially great for more complex responsive layouts, because you can completely re-structure the layout at a breakpoint by just re-defining the template-areas string. You don’t need to shuffle DOM order or anything.

minmax is one of those things that sounds boring but is actually incredibly useful. You’d do minmax(200px, 1fr) and it would mean “do not go smaller than 200px, but grow to fill the space if there’s available room”. This is perfect for responsive grid layouts when you want the columns to shrink down to a point, but not get so small they’re unusable. Combine it with auto-fit or auto-fill with repeat() and you can get completely auto-responsive grids that don’t even need media queries. The columns just wrap when they run out of room. Like repeat(auto-fit, minmax(250px, 1fr)) as a grid-template-columns value will create a layout that can flow from a single column layout to many based on the viewport width. It’s almost magic…literally feels like cheating.

Speaking of grid lines, the implicit grid is this magic that Grid automatically creates if you have more items than can fit in the number of rows/columns you've explicitly defined. The grid will just create additional rows as needed (by default) and you can control the size of those implicit rows with grid-auto-rows. If you set grid-auto-rows: 200px then all overflow rows (rows that are added implicitly, not explicitly defined) will be 200px tall. Or if you say grid-auto-rows: minmax(100px, auto) then they will at least be 100px tall but can grow as big as necessary to fit content. This is why Grid is so forgiving. You don't have to have an exact count of how many items you have before you start the layout or calculate all of the overflow, the layout just adapts as it needs to. That's really nice for dynamically-generated content coming from a CMS or API where you may not know the total item count upfront.