CSS Grid has been production-ready for years now, and yet a lot of developers are still reaching for Flexbox out of habit — even in situations where Grid would produce cleaner, more maintainable code in half the lines.
This guide isn't a beginner introduction. You already know display: grid exists. This is about the features that take your Grid usage from functional to genuinely good: grid-template-areas for readable layout code, minmax() for intrinsically responsive columns, auto-fill vs auto-fit and why the difference matters, subgrid for alignment across nested components, and container queries for component-level responsiveness. Every section includes real code you can drop straight into a project.
Need a website that's fast, modern, and built to rank on Google? Red Surge Technology designs and builds high-performance websites for small businesses. Learn more about what we do.
Before diving into properties, the single most useful thing to understand about Grid is how it differs from Flexbox at a conceptual level.
Flexbox is content-out. The container adapts to its children. Items determine their own size based on their content, and the container responds. This makes Flexbox excellent for components where you don't know how many items you'll have or how large they'll be — navigation bars, tag lists, button groups, card rows.
Grid is layout-in. You define the structure first, then place items into it. The layout blueprint exists independently of the content. This makes Grid excellent for page-level structures, complex designs where precise placement matters, and any layout where you need consistent alignment across rows and columns simultaneously.
In practice, the best codebases use both: Grid for the skeleton, Flexbox for the components inside each grid area. Keep that division in mind as you read through the examples below.
grid-template-areas is one of the most underused Grid features, and it's one of the best. Instead of tracking column and row numbers in your head, you draw the layout visually in your CSS.
Here's a classic page layout — header, sidebar, main content, and footer:
.page {
display: grid;
grid-template-columns: 260px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
min-height: 100vh;
gap: 0;
}
.page-header { grid-area: header; }
.page-sidebar { grid-area: sidebar; }
.page-main { grid-area: main; }
.page-footer { grid-area: footer; }
The grid-template-areas value is a string map of your layout. Each quoted string is a row, each word within it is a column. Repeating the same name across columns makes that element span those columns. A period (.) represents an empty cell.
The result is layout code that's genuinely readable — you can look at the CSS and understand the structure immediately, without mentally parsing column/row numbers. This also makes responsive adjustments much cleaner:
@media (max-width: 768px) {
.page {
grid-template-columns: 1fr;
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
}
}
At mobile sizes, the sidebar drops below the main content with a single area string change. No fussing with column spans or order properties.
minmax() is the function that makes CSS Grid genuinely powerful for responsive layouts. It defines a size range for a track: it can be as small as the first value, and as large as the second.
.grid {
display: grid;
grid-template-columns: minmax(200px, 1fr) minmax(200px, 2fr);
}
This creates two columns: both have a minimum width of 200px, but the second column takes up twice as much of the available space as the first. The columns are responsive by default — no media queries required.
The real power comes when you combine minmax() with auto-fill or auto-fit:
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
This single declaration creates a fully responsive card grid. Columns are at least 280px wide and stretch to fill available space. As the viewport narrows, columns automatically wrap to new rows. No media queries, no JavaScript — the layout is intrinsically responsive.
auto-fill and auto-fit look nearly identical in most situations, but they diverge in an important edge case: what happens when there are fewer items than would fill a row.
auto-fill always creates as many column tracks as will fit, even if some are empty. If your grid is 900px wide and your columns are minmax(280px, 1fr), you get three columns — whether you have three items or one.
auto-fit collapses empty tracks. If you only have one item in a three-column row, auto-fit collapses the two empty columns and the single item stretches to fill the full width.
/* auto-fill: empty columns remain, items don't stretch */
.grid-fill {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
/* auto-fit: empty columns collapse, items stretch to fill */
.grid-fit {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
When to use which:
Use auto-fill when you want a consistent grid structure regardless of content — for example, a product grid where you want placeholder or skeleton cards to maintain their positions.
Use auto-fit when you want items to naturally fill the available space — for example, a feature list where the last row of three items should stretch evenly across the full width rather than sitting left-aligned in a three-column grid.
Auto-placement is convenient, but Grid becomes significantly more powerful when you place items explicitly. This is what enables magazine-style layouts, overlapping elements, and hero sections with precise structure.
The key properties are grid-column and grid-row, which accept line numbers or named areas:
.featured-article {
grid-column: 1 / 3; /* span from line 1 to line 3 (two columns) */
grid-row: 1 / 3; /* span from line 1 to line 3 (two rows) */
}
The shorthand span keyword is often cleaner when you care about size rather than position:
.featured-article {
grid-column: span 2; /* span 2 columns from wherever it's placed */
grid-row: span 2;
}
A real-world example — a blog index with a featured large post and smaller posts beside it:
.blog-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto;
gap: 1.5rem;
}
.blog-grid__featured {
grid-column: span 2;
grid-row: span 2;
}
The featured post takes up a 2×2 area, while the remaining posts auto-place into the remaining slots. This kind of layout used to require complex JavaScript or deeply nested HTML — Grid does it in a handful of CSS declarations.
As layouts get more complex, tracking column line numbers becomes error-prone. Named lines solve this:
.layout {
display: grid;
grid-template-columns:
[full-start] 1rem
[content-start] 1fr
[content-end] 1rem
[full-end];
}
.full-width-banner {
grid-column: full-start / full-end;
}
.content-block {
grid-column: content-start / content-end;
}
This pattern is particularly useful for editorial layouts where some elements (like a hero image or a pull quote) need to break out of the content column and span the full page width. Instead of calculating negative margins or using absolute positioning, you define the full-width track once and reference it by name throughout.
Subgrid is one of the most significant Grid additions in recent years, and as of 2026 it has full browser support across Chrome, Firefox, Safari, and Edge. It solves a problem that was genuinely painful before: aligning elements inside nested components with the parent grid.
The classic example is a set of cards where each card has a header, body, and footer. Without subgrid, you'd either need JavaScript to match heights, or accept that the footers won't align across cards when content lengths vary.
With subgrid:
.card-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
.card {
display: grid;
grid-row: span 3; /* the card spans 3 rows in the parent */
grid-template-rows: subgrid; /* the card's rows align to the parent grid */
}
.card__header { /* aligns with all other card headers automatically */ }
.card__body { /* aligns with all other card bodies automatically */ }
.card__footer { /* aligns with all other card footers automatically */ }
With grid-template-rows: subgrid, the card's internal rows participate in the parent grid's row tracks. Headers line up with headers, footers line up with footers — regardless of how much text each card contains. No JavaScript, no fixed heights, no padding hacks.
Traditional media queries respond to the viewport size, which creates a well-known problem: a component that works perfectly at 600px viewport width might be placed inside a narrow sidebar where it only has 300px of available space. Media queries can't know that — but container queries can.
Container queries let you apply styles based on the size of a component's container, not the viewport:
/* Define the container */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Apply styles based on the container's width */
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 140px 1fr;
grid-template-areas:
"image title"
"image body"
"image footer";
}
}
@container card (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
}
}
Now the card component switches between a stacked layout and a horizontal image-plus-content layout based on the width of its own container — not the viewport. Drop it into a full-width section and it goes horizontal. Drop it into a narrow sidebar and it stacks. The same component, zero extra CSS.
Container queries are fully supported in all modern browsers and should be part of your standard toolkit for any component-based workflow in 2026.
Let's put several of these techniques together in a single, production-ready page layout:
.page {
display: grid;
grid-template-columns:
[full-start] minmax(1rem, 1fr)
[content-start] minmax(0, 75ch)
[content-end] minmax(1rem, 1fr)
[full-end];
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
". main ."
"footer footer footer";
min-height: 100vh;
}
/* Header and footer span full width */
.page-header { grid-area: header; }
.page-footer { grid-area: footer; }
/* Main content is constrained to readable line length */
.page-main { grid-area: main; }
/* Full-bleed sections break out of the content column */
.full-bleed {
grid-column: full-start / full-end;
width: 100%;
}
/* Responsive card grid within main content */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1.5rem;
}
/* Cards use subgrid for internal alignment */
.card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
border: 1px solid hsl(0 0% 90%);
border-radius: 0.5rem;
overflow: hidden;
}
/* Container query wrapper for the cards */
.card-wrapper {
container-type: inline-size;
}
This layout handles full-width sections with no negative margins, constrains readable content to ~75 characters per line, creates an intrinsically responsive card grid, and aligns card internals without fixed heights — all in under 50 lines of CSS.
Reach for Grid when you need to control layout in two dimensions simultaneously — rows and columns. Use it for page-level structures (header, sidebar, main, footer), card grids, complex editorial layouts, and any situation where you need precise, consistent alignment across both axes. Use Flexbox for one-dimensional alignment — navigation bars, button groups, stacking items in a single row or column. In practice, most pages use both: Grid for the overall structure, Flexbox for the components within it.
For many common layout patterns — especially card grids and multi-column content — minmax() with auto-fill or auto-fit produces fully responsive behavior without a single media query. That said, media queries are still valuable for more significant layout shifts, like changing from a sidebar layout to a single-column stacked layout at mobile sizes. The best approach is to use intrinsic Grid techniques as the foundation and only add media queries where the layout genuinely needs a structural change, not just a size adjustment.
Yes. As of 2026, subgrid has universal browser support across Chrome 117+, Firefox 71+, Safari 16+, and Edge 117+. This gives you approximately 97%+ global coverage. For the small fraction of users on unsupported browsers, you can provide a Flexbox fallback using @supports:
.card {
display: flex;
flex-direction: column;
}
@supports (grid-template-rows: subgrid) {
.card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
}
}
fr stands for "fractional unit" — it represents a fraction of the available space in the grid container after any fixed-size tracks have been calculated. 1fr 2fr creates two columns where the second is twice as wide as the first. repeat(3, 1fr) creates three equal columns. Use fr wherever you want columns to grow and fill available space proportionally. Combine it with minmax() to set a minimum size below which the column won't shrink.
Media queries respond to the viewport width. Container queries respond to the width of a specific element's container. This distinction matters enormously for reusable components — a card that needs to switch between a vertical and horizontal layout depending on where it's placed in the page can't use media queries reliably, because the viewport width doesn't tell it how much space its container has. Container queries solve this by letting each component respond to its own available space. Declare container-type: inline-size on the wrapper, then write @container rules on the component itself.
gap (formerly grid-gap) applies spacing between grid tracks, not around the outside of the grid. This means you get consistent gutters between every item without any of the side effects that come with margin — no extra space on the outer edges, no need for negative margin hacks on the container, no math required to make the last item in a row line up correctly. Use gap for internal grid spacing and normal margin/padding for spacing between the grid and surrounding elements.
Written by Collin Stewart, founder of Red Surge Technology. We design and build fast, modern websites for small businesses — with clean code and local SEO built in from day one. Check out our JavaScript Fetch API guide for more frontend tutorials, or learn how much a professional website costs in New Jersey.