Skip to main content

Command Palette

Search for a command to run...

CSS Grid: The Layout System You're Probably Underusing

Updated
9 min readView as Markdown
CSS Grid: The Layout System You're Probably Underusing
N
Love to code, gaming. And I use vim btw.

I'll be honest with you – for the longest time, I avoided CSS Grid. Flexbox felt comfortable, like an old pair of jeans. Grid felt... complicated. Too many lines, too many terms, too much to remember.

Then I actually sat down and learned it properly. And now? I kick myself for not doing it sooner.

CSS Grid isn't just "flexbox but in 2D" (though that's not a bad mental model to start with). It's a fundamentally different way of thinking about layouts. Once it clicks, you'll find yourself reaching for it constantly.

Let me show you why.

The Mental Model That Changed Everything

Here's the thing most tutorials get wrong: they start by throwing syntax at you. grid-template-columns, grid-auto-flow, grid-gap – it's overwhelming.

Instead, think of Grid like this: you're drawing lines on a page, then placing things between those lines.

That's it. Everything else is just different ways to define where those lines go and what sits between them.

When you create a grid, you're creating grid lines – both horizontal and vertical. Your items sit in the spaces between these lines. The visual above shows how items occupy the cells formed by these intersecting lines.

The Basics (But Actually Useful This Time)

Let's start with the minimum you need to know:

.container {
  display: grid;
  grid-template-columns: 200px 200px 200px;
  grid-template-rows: 100px 100px;
  gap: 20px;
}

This creates a 3×2 grid. Three columns, two rows. Simple.

But here's where it gets interesting: instead of fixed pixels, you can use fr units:

.container {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  gap: 20px;
}

Think of fr as "fraction of available space". In the code above, you're saying: "Give the middle column twice as much space as the side columns."

This is where Grid starts to feel magical – it does the math for you. No more calc() nightmares.

Grid Template Areas: The Game Changer

Alright, here's the feature that made me fall in love with Grid. You can literally draw your layout in CSS.

Check this out:

.container {
  display: grid;
  grid-template-columns: 200px 1fr 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header header"
    "sidebar content content"
    "footer footer footer";
  gap: 16px;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer  { grid-area: footer; }

Look at those grid-template-areas. That's not some abstract syntax – that's literally what your layout looks like. The header stretches across the top, sidebar on the left, content takes up the remaining space, footer at the bottom.

The visual above shows exactly how this ASCII-art-like syntax maps to the actual layout. It's readable. It's maintainable. It's genius.

Want to move the sidebar to the right? Just rearrange the strings:

grid-template-areas:
  "header header header"
  "content content sidebar"
  "footer footer footer";

Done. No math. No recalculating widths. Just move the words around.

Grid Hacks That'll Make You Look Like a Wizard

1. The "Dot Trick" for Empty Cells

Sometimes you want gaps in your grid – actual empty spaces, not just gaps between items. Use a dot (.) to represent an empty cell:

grid-template-areas:
  "logo . . nav"
  "sidebar content content content"
  ". footer footer .";

This creates empty cells in your grid. The first row has the logo on the left, nav on the right, and empty space in between. The footer row has padding on both sides. Simple, visual, perfect.

2. Overlapping Items (Yes, Really)

Here's something that blew my mind: you can place multiple items in the same grid cell. They'll overlap.

.container {
  display: grid;
  grid-template-columns: 1fr;
}

.background-image {
  grid-column: 1;
  grid-row: 1;
  z-index: 1;
}

.overlay-text {
  grid-column: 1;
  grid-row: 1;
  z-index: 2;
  align-self: center;
  justify-self: center;
}

Both items occupy column 1, row 1. They stack on top of each other. Use z-index to control the layering. This is perfect for hero sections, image overlays, card designs – anywhere you'd normally reach for position: absolute.

3. Auto-Fit and Auto-Fill: Responsive Without Media Queries

This one's a crowd-pleaser. Want cards that automatically wrap and resize based on container width? No media queries needed:

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 20px;
}

Let me break that down:

  • repeat() creates multiple columns

  • auto-fit makes as many columns as will fit

  • minmax(250px, 1fr) says each column should be at least 250px, but can grow to fill available space

The grid automatically adjusts the number of columns based on available space. On a wide screen? Maybe 4 columns. On a phone? Drops down to 1. All automatic. All smooth.

The difference between auto-fit and auto-fill is subtle but important:

  • auto-fit collapses empty tracks to zero

  • auto-fill keeps empty tracks (maintains column count)

Use auto-fit when you want items to stretch and fill space. Use auto-fill when you want a consistent grid structure even with fewer items.

4. Grid Line Numbers: Your Secret Weapon

Remember how I said Grid is all about lines? Well, those lines have numbers.

The visual above shows how grid lines are numbered. Columns and rows both start at 1 and count up from the top-left corner. But here's the clever bit: they also count backwards from the bottom-right using negative numbers.

So line -1 is always the last line, whether you have 3 columns or 30. This is incredibly useful:

.full-width {
  /* Stretch from first to last line */
  grid-column: 1 / -1;
}

.header {
  /* Also stretches full width */
  grid-column: 1 / -1;
  /* But only in the first row */
  grid-row: 1 / 2;
}

The -1 trick means you never have to count your columns. Want something to span the entire width? grid-column: 1 / -1. Always works.

You can also use the span keyword:

.featured-card {
  /* Start at column 2, span across 3 columns */
  grid-column: 2 / span 3;
}

This is cleaner than counting endpoints manually.

5. Dense Packing with grid-auto-flow

Got items of different sizes and want Grid to pack them efficiently? Add this one line:

.masonry-style {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  grid-auto-flow: dense;
  gap: 16px;
}

The grid-auto-flow: dense tells Grid to fill in gaps whenever possible. If a large item doesn't fit in the current row, Grid will skip it temporarily, place smaller items in the gaps, then come back to the large one.

Warning: this can mess up tab order and screen reader navigation since items won't flow in source order anymore. Use it for image galleries or decorative layouts, not for critical content.

6. Implicit Grids: Let Grid Do the Work

You don't always need to define every row. Sometimes you just want Grid to create rows as needed:

.auto-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  /* Don't define rows – let them be created automatically */
  grid-auto-rows: minmax(100px, auto);
  gap: 20px;
}

This creates 3 columns, and rows are added automatically as you add more items. Each row will be at least 100px tall but can grow to fit content. Perfect for card layouts where you don't know how many rows you'll need.

A Real-World Example: Dashboard Layout

Let's put it all together with something you'd actually build – a dashboard:

.dashboard {
  display: grid;
  grid-template-columns: 250px 1fr 1fr;
  grid-template-rows: 60px 1fr 1fr;
  grid-template-areas:
    "sidebar header header"
    "sidebar main-chart main-chart"
    "sidebar stats-1 stats-2";
  gap: 20px;
  height: 100vh;
  padding: 20px;
}

.sidebar { grid-area: sidebar; }
.header { grid-area: header; }
.main-chart { grid-area: main-chart; }
.stats-1 { grid-area: stats-1; }
.stats-2 { grid-area: stats-2; }

/* Make it responsive */
@media (max-width: 768px) {
  .dashboard {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main-chart"
      "stats-1"
      "stats-2"
      "sidebar";
  }
}

On desktop: sidebar on the left, header across the top, big chart in the middle, two stat cards at the bottom.

On mobile: just stack everything vertically by redefining the template areas. No position changes, no moving elements around in the DOM. Just change the grid definition.

Common Gotchas (So You Don't Have To Learn The Hard Way)

1. Grid Items Create New Stacking Contexts

When you use grid-column or grid-row, you're creating a positioned element. This means it gets its own stacking context. If your z-indexes aren't working like you expect, this might be why.

2. Margins Don't Collapse

Unlike block elements, margins on grid items don't collapse. If you have margin-bottom: 20px on one item and margin-top: 20px on the item below, you get 40px of space, not 20px. Use gap instead for consistent spacing.

3. Percentage Heights Need Explicit Row Heights

This one trips people up:

.grid-item {
  height: 100%; /* This won't work */
}

For percentage heights to work, the grid row needs an explicit height:

.container {
  grid-template-rows: 300px; /* Or 1fr, or minmax(...) */
}

Or use align-self: stretch (which is the default anyway).

When NOT to Use Grid

Real talk: Grid isn't always the answer.

Use Flexbox when:

  • You're laying out items in a single direction (row or column)

  • You want items to size themselves based on content

  • You're building a navigation bar, button group, or simple centered layout

Use Grid when:

  • You need to control both rows and columns

  • You want to overlap items intentionally

  • You're building page layouts, dashboards, card grids, or complex forms

  • You want named template areas for clarity

Sometimes you'll use both in the same project. Grid for the page layout, Flexbox for the navbar. That's fine. Use the right tool for the job.

The Bottom Line

CSS Grid is one of those features that fundamentally changes how you approach layout. Once you internalize the mental model – drawing lines and placing things between them – it becomes second nature.

Start with grid-template-areas for your page layouts. It's the most intuitive way to work with Grid, and it makes your CSS incredibly readable. Then layer in line numbers, auto-fit, and the other tricks as you need them.

And remember: you're not fighting the layout anymore. You're describing what you want, and Grid figures out how to make it happen.

That's the real superpower.


What's your favorite Grid trick? Hit me up in the comments – I'm always looking for new techniques to add to my toolbox.