WEB DEVELOPMENT

Modern CSS Grid Layout

25 min read Intermediate

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.

Tip: CSS Grid and Flexbox are not competitors — they complement each other. Use Grid for the overall page layout (the macro structure) and Flexbox for arranging items within a component (the micro structure). Many modern designs use both together.

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.

Tip: You can use a period (.) 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;
}
Tip: The value -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 layout
  • grid-template-columns — Defines column sizes
  • grid-template-rows — Defines row sizes
  • grid-template-areas — Names grid regions
  • gap — Space between grid items
  • grid-auto-rows — Size of implicit (auto-created) rows
  • justify-items / align-items — Align items within cells
  • justify-content / align-content — Align the grid within the container

Item properties (applied to grid children):

  • grid-column — Which column(s) an item occupies
  • grid-row — Which row(s) an item occupies
  • grid-area — Assigns an item to a named area, or shorthand for row/column placement
  • justify-self / align-self — Override alignment for a single item
Tip: Browser developer tools have excellent Grid inspectors. In Chrome or Firefox, look for the "grid" badge next to grid containers in the Elements panel. Clicking it overlays grid lines on the page, making it much easier to debug your layouts.

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.