Skip to main content

🔲 Lesson 14: CSS Grid Basics

Flexbox handles one direction brilliantly — a row or a column. But what about a full page layout with a header, sidebar, main content, and footer? Or a photo gallery with precise rows and columns? CSS Grid is the two-dimensional layout system built for exactly this. If Flexbox is a clothesline, Grid is a spreadsheet.

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Explain how CSS Grid differs from Flexbox and when to use each
  • Create a grid container with display: grid
  • Define columns and rows with grid-template-columns and grid-template-rows
  • Use the fr unit to create flexible, proportional columns
  • Place items into specific grid cells using line numbers
  • Name grid areas with grid-template-areas for readable page layouts
  • Control spacing with gap
  • Build a full page layout with header, sidebar, content, and footer

Estimated Time: 60 minutes

Hands-on: You'll build a complete page layout and a responsive image gallery.

📑 In This Lesson

Grid vs. Flexbox — When to Use Which

Flexbox and Grid aren't competitors — they're partners. Each excels at a different type of layout problem:

Flexbox vs. Grid
Aspect Flexbox CSS Grid
Dimensions One-dimensional (row or column) Two-dimensional (rows and columns)
Best for Components: navbars, card rows, button groups Page layouts: headers, sidebars, content areas, galleries
Content vs. Layout Content-driven — items determine how space is used Layout-driven — you define the grid, items fill it
Item placement Items flow in order along the main axis Items can be placed in any cell, in any order

Rule of thumb: Use Flexbox for components (things inside a page section). Use Grid for page-level structure (the sections themselves). And use them together — a Grid layout whose cells contain Flexbox components is completely normal.

graph TD A["Page Layout = CSS Grid"] --> B["Header
(Flexbox inside)"] A --> C["Sidebar"] A --> D["Main Content"] A --> E["Footer
(Flexbox inside)"] D --> F["Card Row
(Flexbox)"] D --> G["Image Gallery
(Grid)"] style A fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#1e293b style B fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style C fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style D fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style E fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style F fill:#fffbeb,stroke:#f59e0b,stroke-width:2px,color:#1e293b style G fill:#fffbeb,stroke:#f59e0b,stroke-width:2px,color:#1e293b

Creating a Grid

Just like Flexbox, Grid starts with the parent. Apply display: grid to the container, then define columns and rows:

.grid-container {
    display: grid;
    grid-template-columns: 200px 1fr 200px;
    grid-template-rows: auto 1fr auto;
}

That one rule creates a 3-column, 3-row grid. The children automatically fill the cells in order — left to right, top to bottom — like reading a book.

Defining Columns

grid-template-columns defines how many columns you want and how wide each one is:

/* Three equal columns, each 200px */
.grid { grid-template-columns: 200px 200px 200px; }

/* Two columns: one fixed, one flexible */
.grid { grid-template-columns: 250px 1fr; }

/* Four columns: 25% each */
.grid { grid-template-columns: 25% 25% 25% 25%; }

/* Three columns using the fr unit (more on this next) */
.grid { grid-template-columns: 1fr 2fr 1fr; }

Defining Rows

grid-template-rows works the same way, but for rows:

/* Explicit row heights */
.grid { grid-template-rows: 80px 1fr 60px; }

/* Auto-sized rows (height fits content) */
.grid { grid-template-rows: auto auto auto; }

If you don't define rows explicitly, Grid creates them automatically as items are added. These implicit rows default to auto height (fitting their content). You can control their size with grid-auto-rows:

/* All auto-created rows will be at least 150px tall */
.grid {
    grid-template-columns: 1fr 1fr 1fr;
    grid-auto-rows: minmax(150px, auto);
}

The fr Unit — Fractional Space

The fr (fraction) unit is Grid's killer feature. It represents a share of the available space — after fixed-size tracks and gaps are accounted for.

/* Three equal columns */
.grid { grid-template-columns: 1fr 1fr 1fr; }

/* Sidebar (1 share) + Main content (3 shares) */
.grid { grid-template-columns: 1fr 3fr; }

/* Fixed sidebar + flexible main */
.grid { grid-template-columns: 250px 1fr; }

In the 1fr 3fr example, the available space is divided into 4 shares. The first column gets 1/4, the second gets 3/4. If the container is 800px wide, that's 200px and 600px.

graph LR subgraph "grid-template-columns: 1fr 3fr (800px container)" A["1fr = 200px"] --- B["3fr = 600px"] end style A fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style B fill:#eff6ff,stroke:#6366f1,stroke-width:2px,color:#1e293b

fr is more predictable than percentages because it accounts for gaps automatically. With percentages, you'd need to subtract the gap from 100% yourself. With fr, the browser handles it.

💡 Mixing Units

You can mix fr with fixed units freely. The fixed tracks get their exact size first, then the remaining space is split among the fr tracks. This is incredibly powerful for layouts like "fixed sidebar + flexible main content":

.layout { grid-template-columns: 250px 1fr; }
/* Sidebar is always 250px. Main content gets everything else. */

repeat() and minmax()

Typing 1fr 1fr 1fr 1fr for twelve equal columns gets tedious. The repeat() function saves you:

/* 3 equal columns */
.grid { grid-template-columns: repeat(3, 1fr); }

/* 12-column grid (like Bootstrap) */
.grid { grid-template-columns: repeat(12, 1fr); }

/* Pattern: 200px then 1fr, repeated 3 times */
.grid { grid-template-columns: repeat(3, 200px 1fr); }
/* Result: 200px 1fr 200px 1fr 200px 1fr */

minmax() — Flexible Sizing with Limits

minmax(min, max) sets a size range. The track will be at least min and at most max:

/* Rows at least 100px tall, but grow to fit content */
.grid { grid-auto-rows: minmax(100px, auto); }

/* Columns at least 200px, at most 1fr */
.grid { grid-template-columns: repeat(3, minmax(200px, 1fr)); }

Auto-fit and Auto-fill — Responsive Without Media Queries

The real magic happens when you combine repeat(), auto-fit, and minmax():

/* Responsive grid: as many 250px+ columns as will fit */
.gallery {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 20px;
}

This single line creates a fully responsive grid. On a wide screen, you get four or five columns. On a tablet, three. On a phone, one or two. No media queries. The browser figures out how many 250px-minimum columns fit and distributes the remaining space equally.

auto-fit vs. auto-fill
Keyword Behavior When to Use
auto-fit Creates columns, then collapses empty ones so items stretch to fill Most cases — items expand to use all available space
auto-fill Creates columns but keeps empty ones as visible gaps When you want consistent column widths even with few items

In practice, auto-fit is what you want 90% of the time.

gap — Spacing Between Cells

Just like in Flexbox, gap adds space between grid cells without affecting the outer edges:

.grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;              /* 20px between all rows and columns */
}

/* Different row and column gaps */
.grid {
    gap: 20px 16px;         /* 20px between rows, 16px between columns */
}

/* Individual properties */
.grid {
    row-gap: 24px;
    column-gap: 16px;
}

An important advantage over using margins: gap only creates space between cells. There's no extra space on the outer edges, so you don't need to fight with :first-child or :last-child overrides.

Placing Items on the Grid

By default, grid items fill cells in order. But you can place any item in any cell — or make it span multiple cells — using grid line numbers.

Understanding Grid Lines

Grid lines are the invisible lines between and around tracks. A 3-column grid has 4 column lines (numbered 1 through 4):

graph LR subgraph "3-Column Grid Lines" direction LR L1["Line 1"] --- C1["Column 1"] --- L2["Line 2"] --- C2["Column 2"] --- L3["Line 3"] --- C3["Column 3"] --- L4["Line 4"] end style L1 fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b style L2 fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b style L3 fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b style L4 fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1e293b style C1 fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style C2 fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style C3 fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b

Placing Items by Line Number

/* Place an item from column line 1 to column line 3 (spans 2 columns) */
.header {
    grid-column: 1 / 3;
}

/* Place an item from row line 1 to row line 3 (spans 2 rows) */
.sidebar {
    grid-row: 1 / 3;
}

/* Span all columns (use -1 for the last line) */
.footer {
    grid-column: 1 / -1;
}

The span Keyword

Instead of counting line numbers, you can say "span N tracks":

/* Span 2 columns from wherever the item naturally lands */
.feature-card {
    grid-column: span 2;
}

/* Span 2 rows */
.tall-card {
    grid-row: span 2;
}

/* Span 2 columns AND 2 rows */
.big-card {
    grid-column: span 2;
    grid-row: span 2;
}

Shorthand: grid-column and grid-row

The shorthand grid-column combines grid-column-start and grid-column-end with a slash:

/* These are equivalent: */
.item { grid-column-start: 2; grid-column-end: 4; }
.item { grid-column: 2 / 4; }
.item { grid-column: 2 / span 2; }

grid-template-areas — Named Layouts

This is many developers' favorite Grid feature. Instead of juggling line numbers, you draw your layout as a text map:

.page {
    display: grid;
    grid-template-columns: 250px 1fr;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
        "header  header"
        "sidebar main"
        "footer  footer";
    min-height: 100vh;
    gap: 0;
}

Then assign each child to its named area:

.page-header  { grid-area: header; }
.page-sidebar { grid-area: sidebar; }
.page-main    { grid-area: main; }
.page-footer  { grid-area: footer; }
<div class="page">
    <header class="page-header">Header</header>
    <aside class="page-sidebar">Sidebar</aside>
    <main class="page-main">Main Content</main>
    <footer class="page-footer">Footer</footer>
</div>

The beauty of this approach: the layout is readable as plain text. You can see the structure at a glance — "header" spans both columns, "sidebar" and "main" sit side by side, "footer" spans both columns again.

graph TD subgraph "grid-template-areas" H["header     header"] S["sidebar"] M["main"] F["footer     footer"] end style H fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b style S fill:#f0fdf4,stroke:#22c55e,stroke-width:2px,color:#1e293b style M fill:#fffbeb,stroke:#f59e0b,stroke-width:2px,color:#1e293b style F fill:#eff6ff,stroke:#3b82f6,stroke-width:2px,color:#1e293b

💡 Area Rules

  • Each row in the template is a quoted string
  • Area names must form rectangles — no L-shapes or T-shapes
  • Use a . (dot) for an empty cell: "sidebar . main"
  • The number of names in each row must match the number of columns

Alignment in Grid

Grid offers alignment along both axes simultaneously — something Flexbox can only do in one direction at a time.

Container-Level Alignment

Grid Alignment Properties (Container)
Property Axis What It Does
justify-items Inline (horizontal) Aligns all items horizontally within their cells
align-items Block (vertical) Aligns all items vertically within their cells
place-items Both Shorthand: align-items / justify-items
justify-content Inline (horizontal) Aligns the entire grid within the container
align-content Block (vertical) Aligns the entire grid within the container
place-content Both Shorthand: align-content / justify-content

Item-Level Alignment

Override alignment for individual items with justify-self and align-self:

/* Center everything in the grid by default */
.grid {
    display: grid;
    place-items: center;
}

/* But this one item aligns to the end */
.special {
    justify-self: end;
    align-self: start;
}

Centering in Grid — Even Simpler

Grid offers arguably the simplest centering syntax in CSS:

.centered {
    display: grid;
    place-items: center;
    min-height: 100vh;
}

One property — place-items: center — centers all children both horizontally and vertically. Even simpler than the Flexbox version.

Common Grid Patterns

Pattern 1: Classic Page Layout (Header, Sidebar, Main, Footer)

.page-layout {
    display: grid;
    grid-template-columns: 250px 1fr;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
        "header  header"
        "sidebar main"
        "footer  footer";
    min-height: 100vh;
}

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

Pattern 2: Responsive Image Gallery

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

.gallery img {
    width: 100%;
    height: 200px;
    object-fit: cover;
    border-radius: 8px;
}

This creates a gallery that automatically adjusts from one column on small screens to as many as five or six on large screens — all without media queries.

Pattern 3: Dashboard Cards

.dashboard {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 24px;
}

/* Feature card spans two columns */
.dashboard .featured {
    grid-column: span 2;
}

Pattern 4: Holy Grail Layout (No Sidebar on Mobile)

.page {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-areas:
        "header"
        "main"
        "sidebar"
        "footer";
}

/* On larger screens, add the sidebar column */
@media (min-width: 768px) {
    .page {
        grid-template-columns: 250px 1fr;
        grid-template-areas:
            "header  header"
            "sidebar main"
            "footer  footer";
    }
}

This is a sneak peek at responsive design (covered fully in the next lesson). The key idea: redefine grid-template-areas at different screen sizes to completely rearrange the page layout.

Pattern 5: Equal-Height Cards

/* Grid makes all cards in a row the same height automatically */
.card-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
}

/* Each card stretches to fill its cell (default behavior) */
.card-grid .card {
    display: flex;
    flex-direction: column;
}

/* Push the button to the bottom of the card */
.card-grid .card .btn {
    margin-top: auto;
}

Notice the Grid + Flexbox combo: Grid handles the outer layout (equal-height columns), while Flexbox inside each card pushes the button to the bottom. This is the power of using both systems together.

Hands-on Exercise

🏋️ Exercise: Grid Page Layout & Gallery

Objective: Build a complete page layout using grid-template-areas and create a responsive image gallery.

Part A — Page Layout:

  1. In your css/style.css, create a .page-grid class with display: grid
  2. Define two columns: 220px 1fr
  3. Define three rows: auto 1fr auto
  4. Use grid-template-areas to create a header (full width), sidebar + main (middle row), and footer (full width)
  5. Set min-height: 100vh so the layout fills the viewport
  6. Assign each section its grid area name
  7. Add some padding and background colors to each section so you can see the layout

Part B — Responsive Gallery:

  1. Inside your main content area, create a <div class="gallery">
  2. Add 6–8 placeholder images (use https://picsum.photos/400/300 for placeholder images)
  3. Style the gallery with display: grid
  4. Use grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  5. Add gap: 16px;
  6. Style images with width: 100%; height: 200px; object-fit: cover; border-radius: 8px;
  7. Resize your browser and watch columns appear and disappear automatically
💡 Hint

For the page layout, remember that grid-template-areas uses quoted strings — one string per row. The "header" name should appear twice in the first row (once for each column) so it spans the full width. Same for the footer. Use min-height: 100vh so the middle row stretches even if there's not much content.

✅ Example Solution
/* ===========================
   Page Layout (Grid)
   =========================== */
.page-grid {
    display: grid;
    grid-template-columns: 220px 1fr;
    grid-template-rows: auto 1fr auto;
    grid-template-areas:
        "header  header"
        "sidebar main"
        "footer  footer";
    min-height: 100vh;
}

.page-header {
    grid-area: header;
    background-color: var(--color-primary, #1e293b);
    color: white;
    padding: 16px 24px;
}

.page-sidebar {
    grid-area: sidebar;
    background-color: var(--color-bg-alt, #f8fafc);
    padding: 20px;
    border-right: 1px solid var(--color-border, #e2e8f0);
}

.page-main {
    grid-area: main;
    padding: 24px;
}

.page-footer {
    grid-area: footer;
    background-color: var(--color-bg-alt, #f8fafc);
    padding: 16px 24px;
    border-top: 1px solid var(--color-border, #e2e8f0);
    text-align: center;
    font-size: 0.875rem;
    color: var(--color-text-light, #64748b);
}

/* ===========================
   Responsive Gallery (Grid)
   =========================== */
.gallery {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 16px;
    margin-top: 24px;
}

.gallery img {
    width: 100%;
    height: 200px;
    object-fit: cover;
    border-radius: 8px;
    transition: transform 0.2s ease;
}

.gallery img:hover {
    transform: scale(1.03);
}
<div class="page-grid">
    <header class="page-header">
        <h1>My Website</h1>
        <!-- Flex nav inside grid header -->
    </header>

    <aside class="page-sidebar">
        <nav>
            <h3>Navigation</h3>
            <ul>
                <li><a href="index.html">Home</a></li>
                <li><a href="about.html">About</a></li>
                <li><a href="recipe.html">Recipes</a></li>
                <li><a href="contact.html">Contact</a></li>
            </ul>
        </nav>
    </aside>

    <main class="page-main">
        <h2>Photo Gallery</h2>
        <div class="gallery">
            <img src="https://picsum.photos/400/300?random=1" alt="Gallery photo 1">
            <img src="https://picsum.photos/400/300?random=2" alt="Gallery photo 2">
            <img src="https://picsum.photos/400/300?random=3" alt="Gallery photo 3">
            <img src="https://picsum.photos/400/300?random=4" alt="Gallery photo 4">
            <img src="https://picsum.photos/400/300?random=5" alt="Gallery photo 5">
            <img src="https://picsum.photos/400/300?random=6" alt="Gallery photo 6">
        </div>
    </main>

    <footer class="page-footer">
        <p>&copy; 2026 Alex Johnson. Built with HTML & CSS.</p>
    </footer>
</div>

🎯 Quick Quiz

Question 1: What is the main difference between Flexbox and CSS Grid?

Question 2: What does 1fr mean in CSS Grid?

Question 3: What does grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); do?

Question 4: How do you make a grid item span two columns?

Question 5: What is grid-template-areas used for?

Summary

🎉 Key Takeaways

  • CSS Grid is two-dimensional — it controls rows and columns at the same time
  • Use Flexbox for components (navbars, card rows) and Grid for page layouts (header/sidebar/main/footer)
  • grid-template-columns and grid-template-rows define the grid structure
  • The fr unit distributes remaining space proportionally — better than percentages because it accounts for gaps
  • repeat(auto-fit, minmax(250px, 1fr)) creates a fully responsive grid with no media queries
  • Place items with line numbers (grid-column: 1 / 3) or the span keyword (grid-column: span 2)
  • grid-template-areas lets you draw your layout as a readable text map — the most intuitive way to define page structure
  • place-items: center is the simplest way to center content in CSS
  • Grid + Flexbox together is the standard approach: Grid for the outer page, Flexbox for components inside each grid cell

📁 Your Project So Far

my-website/
├── css/
│   └── style.css      ← now with Grid page layout + gallery
├── images/
│   └── sunset.jpg
├── index.html         ← grid-based page structure
├── about.html
├── recipe.html
└── contact.html

🚀 What's Next?

You can now build sophisticated layouts with Flexbox and Grid. But what happens when someone visits your site on a phone? Or a giant desktop monitor? A layout that works at one screen size might be cramped or wasted at another. In the next lesson, you'll learn Responsive Design — using media queries, fluid sizing, and mobile-first thinking to make your site look great on every device.