Modern CSS Grid Layout
What is CSS Grid?
CSS Grid Layout is a two-dimensional layout system designed specifically for the web. Unlike Flexbox, which works in one dimension (either a row or a column), CSS Grid lets you control both rows and columns simultaneously. This makes it the ideal tool for building complex page layouts, card grids, dashboards, and any design that requires precise control over both axes.
Before CSS Grid, developers relied on floats, tables, and complicated positioning hacks to create multi-column layouts. Grid eliminates all of that complexity and gives you a clean, declarative way to describe exactly how your layout should look.
Getting Started: display: grid
To create a grid, you simply set display: grid on a container element. All direct children of that container automatically become grid items. By itself, this does not change the visual appearance much — you need to define columns and rows to see the grid in action.
/* HTML */
<div class="grid-container">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
</div>
/* CSS */
.grid-container {
display: grid;
grid-template-columns: 200px 200px 200px;
gap: 16px;
}
.item {
background: #3498db;
color: white;
padding: 20px;
text-align: center;
border-radius: 8px;
}
This creates a three-column grid where each column is exactly 200 pixels wide. The gap property adds 16px of space between all grid items (both horizontally and vertically). The six items automatically flow into two rows of three.
The fr Unit: Flexible Fractions
Fixed pixel widths are useful sometimes, but for responsive design you want columns that share available space proportionally. The fr (fraction) unit was designed specifically for CSS Grid. It represents a fraction of the remaining free space in the grid container.
/* Three equal-width columns */
.grid-container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
/* Sidebar + main content layout */
.page-layout {
display: grid;
grid-template-columns: 250px 1fr;
gap: 30px;
}
/* Three columns: sidebar, main, sidebar */
.holy-grail {
display: grid;
grid-template-columns: 200px 1fr 200px;
gap: 20px;
}
/* Mixing fr units for proportional widths */
.proportional {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
gap: 16px;
/* First column: 25%, Second: 50%, Third: 25% */
}
In the proportional example, the total is 4fr (1 + 2 + 1). The first and third columns each get 1/4 of the space, while the middle column gets 2/4 (half). This is far more intuitive than calculating percentages and accounting for gaps.
Defining Rows with grid-template-rows
Just as you define columns, you can explicitly define row sizes. If you do not define rows, Grid creates them automatically (implicit rows) and sizes them to fit their content.
/* Explicit rows */
.grid-container {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 100px 200px 100px;
gap: 16px;
}
/* Full page layout with header, content, footer */
.page {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
/* Control implicit row sizing */
.auto-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: minmax(150px, auto);
gap: 16px;
}
The grid-auto-rows property controls the height of rows that are created automatically. Using minmax(150px, auto) means each row will be at least 150px tall but will grow taller if the content requires more space.
The repeat() Function
Writing 1fr 1fr 1fr 1fr for a four-column grid gets tedious. The repeat() function lets you define repeating patterns more concisely.
/* Instead of: 1fr 1fr 1fr 1fr */
.grid {
grid-template-columns: repeat(4, 1fr);
}
/* Repeating a pattern */
.pattern-grid {
grid-template-columns: repeat(3, 100px 1fr);
/* Creates: 100px 1fr 100px 1fr 100px 1fr */
/* That's 6 columns with an alternating pattern */
}
/* 12-column grid system */
.twelve-col {
grid-template-columns: repeat(12, 1fr);
}
Grid Template Areas
One of the most intuitive features of CSS Grid is grid-template-areas. It lets you name regions of your grid and assign items to those regions by name. This creates a visual map of your layout right in your CSS.
/* HTML */
<div class="page-layout">
<header class="header">Header</header>
<nav class="sidebar">Sidebar</nav>
<main class="content">Main Content</main>
<footer class="footer">Footer</footer>
</div>
/* CSS */
.page-layout {
display: grid;
grid-template-columns: 250px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
min-height: 100vh;
gap: 0;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.content { grid-area: content; }
.footer { grid-area: footer; }
Notice how the grid-template-areas property reads like an ASCII art version of your layout. The header and footer span both columns (they appear twice in their row), while the sidebar and content each occupy one column in the middle row.
.) in grid-template-areas to leave a cell empty. For example: "header header" ". content" "footer footer" would leave the sidebar cell empty in the middle row.
Responsive Grids with auto-fit and auto-fill
This is where CSS Grid truly shines. Using auto-fit or auto-fill with minmax(), you can create responsive grids that automatically adjust the number of columns based on the available space — without a single media query.
/* The magic one-liner for responsive grids */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
}
This single line of CSS creates a grid where:
- Each column is at least 280px wide
- Columns expand equally to fill remaining space (
1fr) - As the viewport shrinks, columns drop to the next row automatically
- On a wide screen you might get 4 columns; on a tablet, 2; on mobile, 1
The difference between auto-fit and auto-fill:
/* auto-fit: collapses empty tracks, items stretch to fill space */
.auto-fit-grid {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
/* auto-fill: keeps empty tracks, items maintain minimum size */
.auto-fill-grid {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
/*
If you have 2 items in a container wide enough for 4 columns:
- auto-fit: 2 items stretch across the full width (2 wide columns)
- auto-fill: 2 items stay at their min size, empty tracks remain
In most cases, auto-fit is what you want.
*/
The minmax() Function
The minmax() function defines a size range for a grid track. It takes two arguments: a minimum size and a maximum size. This is extremely useful for creating flexible layouts that never get too small or too large.
/* Column is at least 200px, at most 1fr */
.grid {
grid-template-columns: minmax(200px, 1fr) 2fr;
}
/* Rows are at least 100px, grow with content */
.grid {
grid-auto-rows: minmax(100px, auto);
}
/* Sidebar: min 200px, max 300px. Main: fills rest */
.layout {
grid-template-columns: minmax(200px, 300px) 1fr;
}
/* Combining with repeat for responsive cards */
.cards {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
Placing Items on the Grid
By default, grid items flow into the next available cell automatically. But you can place items precisely using line numbers. Grid lines are numbered starting at 1 from the left (for columns) and from the top (for rows).
/* HTML */
<div class="grid">
<div class="featured">Featured Article</div>
<div class="item">Article 2</div>
<div class="item">Article 3</div>
<div class="item">Article 4</div>
<div class="item">Article 5</div>
</div>
/* CSS */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 200px;
gap: 16px;
}
/* Make the featured item span 2 columns and 2 rows */
.featured {
grid-column: 1 / 3; /* Start at line 1, end at line 3 */
grid-row: 1 / 3; /* Start at line 1, end at line 3 */
}
/* Shorthand using span */
.featured {
grid-column: span 2; /* Span 2 columns from current position */
grid-row: span 2; /* Span 2 rows from current position */
}
More placement examples:
/* Place an item in a specific cell */
.item {
grid-column: 2; /* Second column */
grid-row: 1; /* First row */
}
/* Span the full width (in a 4-column grid) */
.full-width {
grid-column: 1 / -1; /* -1 means the last line */
}
/* Place with start and span */
.item {
grid-column: 2 / span 2; /* Start at column 2, span 2 columns */
}
/* Using grid-area shorthand (row-start / col-start / row-end / col-end) */
.item {
grid-area: 1 / 2 / 3 / 4;
}
-1 refers to the very last grid line. So grid-column: 1 / -1 means "span from the first line to the last line," which is the full width of the grid regardless of how many columns there are.
Alignment and Justification
CSS Grid provides powerful alignment properties for both the grid tracks within the container and the items within their cells.
/* Aligning items within their grid cells */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
/* Align all items horizontally within their cells */
justify-items: center; /* start | end | center | stretch (default) */
/* Align all items vertically within their cells */
align-items: center; /* start | end | center | stretch (default) */
/* Shorthand for both */
place-items: center; /* Centers both horizontally and vertically */
}
/* Aligning the entire grid within the container */
.grid {
justify-content: center; /* Horizontal alignment of the grid */
align-content: center; /* Vertical alignment of the grid */
place-content: center; /* Both at once */
}
/* Override alignment for a single item */
.special-item {
justify-self: end;
align-self: start;
}
The gap Property
The gap property (formerly grid-gap) controls the spacing between grid tracks. It is much cleaner than using margins on individual items because it only adds space between items, not around the outer edges.
/* Equal gap on all sides */
.grid {
gap: 20px;
}
/* Different row and column gaps */
.grid {
row-gap: 30px;
column-gap: 16px;
}
/* Shorthand: row-gap then column-gap */
.grid {
gap: 30px 16px;
}
Real-World Example: Responsive Card Layout
Let's build a complete, real-world responsive card layout that you might use for a portfolio, blog, or product listing page:
/* HTML */
<div class="card-grid">
<div class="card featured">
<img src="featured.jpg" alt="Featured project">
<h3>Featured Project</h3>
<p>This is my best work and it deserves extra space.</p>
</div>
<div class="card">
<img src="project1.jpg" alt="Project 1">
<h3>Project One</h3>
<p>A brief description of this project.</p>
</div>
<div class="card">
<img src="project2.jpg" alt="Project 2">
<h3>Project Two</h3>
<p>Another great project I worked on.</p>
</div>
<div class="card">
<img src="project3.jpg" alt="Project 3">
<h3>Project Three</h3>
<p>A collaboration with a talented team.</p>
</div>
<div class="card">
<img src="project4.jpg" alt="Project 4">
<h3>Project Four</h3>
<p>My latest creation.</p>
</div>
</div>
/* CSS */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 24px;
padding: 24px;
}
.card {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.card img {
width: 100%;
height: 200px;
object-fit: cover;
}
.card h3, .card p {
padding: 0 16px;
}
/* Featured card spans 2 columns on wider screens */
.card.featured {
grid-column: span 2;
}
/* On small screens, featured card goes back to 1 column */
@media (max-width: 640px) {
.card.featured {
grid-column: span 1;
}
}
Real-World Example: Dashboard Layout
Here is a complete dashboard layout using grid-template-areas, demonstrating how to build a complex layout with clean, readable CSS:
/* HTML */
<div class="dashboard">
<header class="dash-header">Dashboard</header>
<nav class="dash-nav">Navigation</nav>
<div class="dash-stats">Stats Bar</div>
<main class="dash-main">Main Content</main>
<aside class="dash-side">Side Panel</aside>
<footer class="dash-footer">Footer</footer>
</div>
/* CSS */
.dashboard {
display: grid;
grid-template-columns: 220px 1fr 300px;
grid-template-rows: 60px 80px 1fr 50px;
grid-template-areas:
"header header header"
"nav stats stats"
"nav main side"
"nav footer footer";
min-height: 100vh;
gap: 0;
}
.dash-header { grid-area: header; background: #2c3e50; color: white; }
.dash-nav { grid-area: nav; background: #34495e; color: white; }
.dash-stats { grid-area: stats; background: #ecf0f1; }
.dash-main { grid-area: main; background: #ffffff; }
.dash-side { grid-area: side; background: #f8f9fa; }
.dash-footer { grid-area: footer; background: #2c3e50; color: white; }
/* Responsive: stack on mobile */
@media (max-width: 768px) {
.dashboard {
grid-template-columns: 1fr;
grid-template-rows: auto;
grid-template-areas:
"header"
"nav"
"stats"
"main"
"side"
"footer";
}
}
Summary and Quick Reference
Here is a quick reference of all the Grid properties covered in this tutorial:
Container properties (applied to the grid parent):
display: grid— Enables grid layoutgrid-template-columns— Defines column sizesgrid-template-rows— Defines row sizesgrid-template-areas— Names grid regionsgap— Space between grid itemsgrid-auto-rows— Size of implicit (auto-created) rowsjustify-items/align-items— Align items within cellsjustify-content/align-content— Align the grid within the container
Item properties (applied to grid children):
grid-column— Which column(s) an item occupiesgrid-row— Which row(s) an item occupiesgrid-area— Assigns an item to a named area, or shorthand for row/column placementjustify-self/align-self— Override alignment for a single item
CSS Grid has transformed how we build web layouts. With the techniques in this tutorial, you can create everything from simple card grids to complex application layouts with clean, maintainable CSS. In the next tutorial, we will explore modern JavaScript features (ES6+) that will take your web development skills to the next level.