Skip to main content

Harshal V. LADHE

Mastering CSS Grid: Grid Areas, Item Alignment, and Spanning

Structure with clarity, align with precision.
Published at:
Last updated:
Estimated reading time:15 min read
Series:CSS Grid Fundamentals (2/3)

Introduction

Welcome to Part 2 of the CSS Grid Fundamentals Series!

In Part 1, you laid the groundwork — learning how to define grid containers, set up tracks, and build basic layouts.

Now, it's time to go beyond the basics.

Think of CSS Grid as your layout canvas — and in this part, you'll pick up new tools like a ruler, compass, and highlighter to draw structure, alignment, and flow with precision.

We'll cover:

  • grid-template-areas and named grid lines for clean, visual layout structure
  • Alignment techniques using place-items and place-self
  • Distributing tracks with justify-content and align-content when your grid is smaller than its container
  • Spanning elements across rows and columns — and keeping visual order accessible
  • The hidden mechanics of explicit and implicit grids
  • subgrid, for keeping nested grids aligned to their parent's tracks

These are the techniques that elevate your layouts from functional to flexible and scalable — especially in real-world, component-based frontend projects.

Grid Template Areas

The grid-template-areas property allows you to define your layout using named areas, making the grid structure more readable and easier to maintain — especially for complex page layouts. It acts as a visual blueprint of your design and simplifies the placement of grid items.

.grid {
  display: grid;
  grid-template-areas:
    "header header header"
    "sidebar main main"
    "footer footer footer";
  grid-template-columns: 1fr 2fr 2fr;
  grid-template-rows: auto 1fr auto;
  padding: 1rem;
  gap: 1rem;
}

Each quoted line defines a row in the grid. The repeated area names indicate how many columns that area spans. You can then assign elements to these areas using the grid-area property:

.header {
  grid-area: header;
  background: #f8b400;
  padding: 1rem;
}

.main {
  grid-area: main;
  background: #4caf50;
  padding: 1rem;
}

.sidebar {
  grid-area: sidebar;
  background: #2196f3;
  padding: 1rem;
}

.footer {
  grid-area: footer;
  background: #9c27b0;
  padding: 1rem;
}

Here's the HTML structure:

<div class="grid">
  <header class="header">Header</header>
  <aside class="sidebar">Sidebar</aside>
  <main class="main">Main Content</main>
  <footer class="footer">Footer</footer>
</div>

Explanation:

  • The layout is visually defined using grid-template-areas, making it easier to understand and modify.
  • The grid has 3 columns and 3 rows.
  • Named areas like "header", "sidebar", "main", and "footer" are assigned using grid-area, allowing semantic HTML elements to be positioned without relying on source order.
  • The column layout uses 1fr 2fr 2fr, giving the sidebar less space than the main content.
  • This is a clean, semantic approach to building responsive page sections — no floats or nested containers needed.
grid-template-areas diagramA 3-column, 3-row grid divided into four named areas: header spanning the full width across the top, sidebar in the narrow first column, main spanning the two wider columns beside it, and footer spanning the full width across the bottom.headersidebarmainfooter

👉 Try this hands-on in the Template Areas tab of the interactive playground below.

👉 Want to see this exact technique applied to a full page? The Holy Grail Layout snippet builds the classic header/nav/main/aside/footer layout with grid-template-areas — including a responsive breakpoint that re-flows it into a single column.

Naming Grid Lines Directly

grid-template-areas names whole regions, but you can also name the individual lines that make up your columns and rows — useful when you want semantic placement without carving out full named areas.

.grid {
  display: grid;
  grid-template-columns:
    [sidebar-start] 250px
    [sidebar-end main-start] 1fr
    [main-end];
  gap: 1rem;
}

.sidebar {
  grid-column: sidebar-start / sidebar-end;
}

.main {
  grid-column: main-start / main-end;
}

Each name in square brackets marks a line, and a single line can carry multiple names (like sidebar-end main-start above, marking where one track ends and the next begins). Items then reference those names in grid-column / grid-row instead of raw numbers.

Visual Illustration

Named grid lines diagramA 2-column grid with a narrow sidebar track and a wider main track. Three named lines are labeled above: sidebar-start at the left edge of the sidebar, sidebar-end and main-start together at the shared line between the two tracks, and main-end at the right edge of the main track.sidebarmain[sidebar-start][sidebar-end main-start][main-end]sidebar — grid-column: sidebar-start / sidebar-endmain — grid-column: main-start / main-end

Aligning Grid Items

CSS Grid not only lets you organize content into rows and columns — it also gives you precise control over how items are aligned within their grid cells.

Alignment can be applied at two levels:

Container-Level Alignment

These properties are applied to the grid container and affect all items inside the grid:

  • justify-items — Aligns items horizontally (inline axis) within each cell.
  • align-items — Aligns items vertically (block axis) within each cell.
  • place-items — A shorthand for combining both align-items and justify-items, written in that order: place-items: <align-items> <justify-items>.
.grid {
  display: grid;
  justify-items: center; /* Horizontally center all grid items within their cells */
  align-items: center;   /* Vertically center all grid items within their cells */
}

Visual Reference (Centered Items)

Diagram of justify-items and align-items set to center: every item sits centered in its cell.justify-items: center; align-items: center;

You can also use place-items as a shorthand — remember, the first value is align-items and the second is justify-items, so end start means align-items: end (bottom) then justify-items: start (left):

.grid {
  display: grid;
  place-items: end start; /* align-items: end (bottom), justify-items: start (left) */
}

Visual Reference (Bottom-Left Aligned)

Diagram of place-items set to end start: every item sits at the bottom-left of its cell.place-items: end start;

Item-Level Alignment

While container-level alignment applies to all grid items, item-level alignment lets you override alignment per individual item. These properties are set directly on the grid items themselves:

  • justify-self — Aligns an item horizontally (inline axis) within its grid cell.
  • align-self — Aligns an item vertically (block axis) within its grid cell.
  • place-self — A shorthand for combining align-self and justify-self, written in that order: place-self: <align-self> <justify-self>.
.item {
  justify-self: end; /* Aligns this item to the right (inline-end) of its cell */
  align-self: start; /* Aligns this item to the top (block-start) of its cell */
}

Visual Reference (Top-Right Alignment)

Diagram of justify-self: end and align-self: start: the item sits at the top-right of its cell.justify-self: end; align-self: start;

You can also use place-self as a shorthand — remember, the first value is align-self and the second is justify-self, so end start means align-self: end (bottom) then justify-self: start (left):

.item {
  place-self: end start; /* align-self: end (bottom), justify-self: start (left) */
}

Visual Reference (Bottom-Left Alignment)

Diagram of place-self: end start: the item sits at the bottom-left of its cell.place-self: end start;

👉 Try this hands-on in the Container Alignment and Self-Alignment panels of the Alignment & Spanning tab in the interactive playground below.

Distributing Grid Tracks

Everything in the previous section aligned an item within its own cell. justify-content and align-content solve a different problem: what happens to the extra space when the grid's tracks, added together, don't fill the container? That only happens when tracks have a fixed size (like 100px) instead of a flexible one (like 1fr, which always grows to consume every available pixel) — so this is the first place in this post where sizing your tracks in fixed units instead of fr actually matters.

  • justify-content — distributes the grid's columns horizontally within the container's leftover width.
  • align-content — distributes the grid's rows vertically within the container's leftover height.
  • place-content — a shorthand for combining both, written place-content: <align-content> <justify-content>.
.grid {
  display: grid;
  grid-template-columns: repeat(3, 80px); /* Fixed tracks — 240px total, container is wider */
  justify-content: space-between;         /* Distribute the extra horizontal space between the columns */
}

Three values cause most of the confusion in practice — space-between, space-around, and space-evenly — and the difference between them is entirely about what happens at the edges:

  • space-between — no space before the first track or after the last; all the extra space goes strictly between tracks.
  • space-around — every track gets equal space on both sides, so the edge gaps end up half the size of the gaps between tracks.
  • space-evenly — every gap, edge or between, is exactly equal.

Visual Illustration

justify-content distribution diagramThree rows, each showing the same 3 fixed-width tracks inside the same container: space-between leaves no gap at the edges, space-around gives each edge half the gap size used between tracks, and space-evenly makes every gap — edge or between — the same size.justify-content: space-between;justify-content: space-around;justify-content: space-evenly;

👉 Try this hands-on in the new Content Distribution tab of the interactive playground below.

Spanning Grid Items

CSS Grid allows items to span across multiple columns or rows using grid-column and grid-row. These properties let you define where an item starts and ends in the grid layout.

Imagine a 3×3 grid:

<div class="grid">
  <div class="item">Spanning</div>
  <div>Item 2</div>
  <div>Item 3</div>
  <div>Item 4</div>
</div>
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* 3 equal columns */
  grid-template-rows: repeat(3, 100px);  /* 3 equal rows */
  gap: 0.5rem;
}

And you apply:

.item {
  grid-column: 1 / 3; /* Spans from column line 1 to 3 → covers columns 1 and 2 */
  grid-row: 2 / 4;    /* Spans from row line 2 to 4 → covers rows 2 and 3 */
}

Visual Reference (Lines Shown)

Grid item spanning diagramA 3x3 grid where Item 2, Item 3, and Item 4 each occupy their own cell in the top row, and a fourth item labeled Spanning covers a merged 2x2 region below them, from grid line 1 to 3 across and from grid line 2 to 4 down. The remaining cell in the bottom right stays empty.Item 2Item 3Item 4Spanning11223344grid-column: 1 / 3 · grid-row: 2 / 4spanning itemregular cell

Shorthand vs Longhand

The grid-column and grid-row properties are shorthands for their respective start and end lines:

.item {
  grid-column: 2 / 4;
  grid-row: 1 / 3;
}

This is functionally the same as:

.item {
  grid-column-start: 2;
  grid-column-end: 4;

  grid-row-start: 1;
  grid-row-end: 3;
}

Shorthand with span

Instead of specifying start and end lines manually, you can use the span keyword to make your code simpler and more flexible:

.item {
  grid-column: span 2; /* Span 2 columns from auto-start */
  grid-row: span 2;    /* Span 2 rows from auto-start */
}

This tells the grid item to span 2 columns and 2 rows starting from its auto-determined position (i.e. where the browser places it in the flow).

👉 Try this hands-on in the Spanning (Hero Item) panel of the Alignment & Spanning tab in the interactive playground below.

Accessibility: Visual Order vs. DOM Order

grid-column and grid-row let you place an item anywhere in the grid — but placement only changes where it appears on screen. It doesn't move the element in the HTML.

That mismatch matters for two groups of users:

  • Keyboard users tab through elements in DOM order, not visual order. If a "Submit" button is moved visually to appear first but is still last in the markup, keyboard focus will jump around in a way that doesn't match what's on screen.
  • Screen reader users hear content read in DOM order by default, so a visually-reordered layout can be announced in a confusing sequence.

Visual Illustration

Visual order vs. DOM order diagramTop panel: Items 1 through 4 in a row, with tab order following them left to right, matching what is on screen. Bottom panel: the same 4 items, but Item 4 now appears first on screen. Tab order still visits Item 1, then Item 2, then Item 3, then loops back to Item 4 in its new, earlier position — a mismatch between what a keyboard or screen reader user experiences and what is shown.Source order = visual orderItem 1Item 2Item 3Item 4Tab order: 1 → 2 → 3 → 4 — matches what's on screen ✓Visually reordered, DOM unchangedItem 4Item 1Item 2Item 3Tab order still goes 1 → 2 → 3 → 4 — but Item 4 now appears first ⚠

If you do need a layout where visual and reading order diverge — for example, a sidebar that appears first on screen but should be read last — test it with keyboard-only navigation (Tab) and a screen reader to confirm the experience still makes sense.

Grid Types

In CSS Grid, it's important to understand the two types of grids: explicit and implicit. These define how grid tracks (rows and columns) are created — either by the developer or automatically by the browser.

Explicit Grids

An explicit grid is one that you define yourself using properties like grid-template-columns and grid-template-rows.

.grid {
  display: grid;
  grid-template-columns: 1fr 1fr;   /* Two equal-width columns */
  grid-template-rows: 100px 100px;  /* Two fixed-height rows */
  gap: 1rem;
}
<div class="grid">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
  <div>Item 4</div>
</div>

In this case, you've explicitly created a 2×2 grid, which gives you 4 cells. Each of the 4 items is placed within this defined layout.

Visual Illustration

An explicit 2x2 grid, defined with grid-template-columns and grid-template-rows, holding 4 items — Item 1 through Item 4 — one per cell.Item 1Item 2Item 3Item 4

If you place more items than your explicit tracks can hold, the grid will automatically generate new rows or columns to accommodate them — these are known as implicit tracks, which we'll explore next.

Implicit Grids

In CSS Grid, if the number of items exceeds the explicitly defined rows or columns, the browser automatically creates additional tracks — these form what's known as the implicit grid.

You can control the size of these extra rows and columns using:

  • grid-auto-rows – defines height of implicit rows.
  • grid-auto-columns – defines width of implicit columns.

Example: Implicit Rows

Let's say you define only 2 rows, but add 5 items:

.grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 100px 100px; /* Explicit: only 2 rows defined */
  gap: 1rem;
}
<div class="grid">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
  <div>Item 4</div>
  <div>Item 5</div> <!-- This item doesn't fit in the defined grid -->
</div>

What happens?

  • The first 4 items fill the 2×2 grid.
  • The 5th item overflows the defined rows.
  • So, the browser creates a 3rd row automatically — this is the implicit grid.

Visual Illustration

Diagram comparing an explicit 2x2 grid to the same grid with a 3rd, auto-created row holding Item 5 and one empty cell.Explicit GridItem 1Item 2Item 3Item 4Implicit GridItem 1Item 2Item 3Item 4Item 5grid-auto-rows sizes the auto-created 3rd rowexplicit trackimplicit track (auto-created)

By default, the height of implicit rows is auto. You can control their size using grid-auto-rows:

.grid {
  grid-auto-rows: 100px; /* Applies height to all implicit rows */
}

👉 Try this hands-on in the Explicit vs Implicit Grid panel of the Alignment & Spanning tab — push the item count past 9 to see the browser add a 3rd row, in the interactive playground below.

Example: Implicit Columns

Now let's say you define only 2 columns, but use grid-auto-flow: column so overflow items push into new columns instead of new rows:

.grid {
  display: grid;
  grid-template-columns: 150px 150px; /* Explicit: only 2 columns defined */
  grid-template-rows: 100px 100px;
  grid-auto-flow: column; /* Place items column-wise */
  grid-auto-columns: 150px; /* Width for any implicitly created columns */
}
<div class="grid">
  <div>Item 1</div>
  <div>Item 2</div>
  <div>Item 3</div>
  <div>Item 4</div>
  <div>Item 5</div> <!-- This item doesn't fit in the defined grid -->
</div>

What happens?

  • With grid-auto-flow: column, items fill column by column: Item 1 and Item 2 fill column 1, Item 3 and Item 4 fill column 2 — using up both explicit columns.
  • Item 5 has nowhere left to go, so the browser generates a 3rd column implicitly rather than wrapping to a new row.
  • That new column is 150px wide (because of grid-auto-columns), and since only Item 5 lands in it, the column's second cell stays empty.

Visual Illustration

Diagram comparing an explicit 2x2 grid to the same grid with a 3rd, auto-created column holding Item 5 and one empty cell.Explicit GridItem 1Item 2Item 3Item 4Implicit GridItem 1Item 2Item 3Item 4Item 5grid-auto-columns sizes the auto-created 3rd columnexplicit trackimplicit track (auto-created)

Use grid-auto-columns to control the width of these new columns.

👉 In that same panel, switch grid-auto-flow to column to see implicit columns generated instead, in the interactive playground below.

Subgrid: Aligning Nested Grids

So far, every grid you've built has been self-contained — a grid item is a black box to its own children. If that item has its own display: grid children, they size themselves independently, with no awareness of the parent's tracks.

subgrid changes that. It lets a nested grid adopt its parent's column or row tracks instead of defining its own — so content inside nested grid items can line up across siblings, even when those siblings have different content lengths.

.cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem;
}

.card {
  display: grid;
  grid-template-rows: subgrid;   /* Inherit the parent's row tracks */
  grid-row: span 3;              /* Occupy 3 of the parent's rows */
}
<div class="cards">
  <div class="card">
    <h3>Title</h3>
    <p>A short description.</p>
    <button>Buy now</button>
  </div>
  <div class="card">
    <h3>A much longer title that wraps</h3>
    <p>A longer description with more detail spanning multiple lines.</p>
    <button>Buy now</button>
  </div>
</div>

Why this matters: without subgrid, each .card sizes its own three rows (title, description, button) independently — so if one card's title wraps to two lines, that card's button drops out of alignment with the others. With grid-template-rows: subgrid, every card's title, description, and button rows are the parent's tracks, so they stay aligned across every card regardless of content length.

Visual Illustration

Subgrid alignment diagramTwo panels, each with two cards containing a title, description, and button. In the left panel, without subgrid, the second card's wrapping title pushes its button below the first card's button. In the right panel, with subgrid, both cards share the same row tracks, so their buttons line up exactly despite the same wrapping title.Without subgridWith subgridTitleDescriptionButtonTitle (wraps)DescriptionButtongapbuttons misalignedTitleDescriptionButtonTitle (wraps)DescriptionButtonbuttons aligned

Common Mistakes with Alignment & Spanning

  • Confusing justify-items/align-items with justify-content/align-content. The -items properties align content inside each cell; the -content properties (see Distributing Grid Tracks above) align the entire track grid within the container when the grid is smaller than its container. Mixing them up is one of the most common Grid debugging rabbit holes.
  • Off-by-one errors with line numbers. Grid lines are numbered starting at 1, not 0 — and a grid with 3 columns has 4 lines, not 3. grid-column: 1 / 3 spans two tracks (columns 1 and 2), not three.
  • Forgetting you can count from the end. Line -1 always refers to the last line in the explicit grid, no matter how many tracks it has — grid-column: 1 / -1 is a quick way to span an item across every column without knowing the exact count.
  • Leaving grid-auto-rows / grid-auto-columns unitless. Unlike fr on explicit tracks, implicit tracks need an explicit unit (grid-auto-rows: 100px, not grid-auto-rows: 1) — a bare number is invalid and the browser falls back to auto.

Interactive Example

Alignment, spanning, and implicit tracks are easiest to internalize together. Use the Alignment & Spanning tab below to set container-level justify-items / align-items for every cell (or flip the checkbox to see the same rule written as the place-items / place-self shorthand), then self-align and span just the highlighted hero item, each in its own panel. The last panel raises the item count past the explicit 3×3 grid so you can watch the browser generate implicit tracks — via grid-auto-flow, grid-auto-rows, and grid-auto-columns — in real time.

Switch to the Content Distribution tab to set justify-content / align-content (or the place-content shorthand) on a grid whose fixed-size tracks are deliberately smaller than their container, and watch space-between, space-around, and space-evenly redraw the gaps around them live.

Switch to the Template Areas tab to revisit named regions from the top of this post — swap between a few preset layouts and watch the same colored blocks reflow into a completely different shape, with the generated grid-template-areas and grid-area rules shown live below the preview.

Grid Alignment, Spanning & Template Areas Playground

Set container-level justify-items/align-items (or the place-items/place-self shorthand), span and self-align a hero item, raise the item count past the explicit grid to watch implicit tracks appear, distribute fixed-size tracks with justify-content/align-content, and switch to the Template Areas tab to swap between named-region layout presets.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>CSS Grid Playground</title>
  </head>
  <body>
    <main class="playground">
      <header class="playground-header">
        <h1>CSS Grid Playground</h1>
        <p>Switch between alignment/spanning and named template areas — everything below updates live.</p>
      </header>

      <div class="tabs" role="tablist" aria-label="Demo mode">
        <button class="tab tab--active" type="button" data-mode="alignment" role="tab" aria-selected="true">Alignment &amp; Spanning</button>
        <button class="tab" type="button" data-mode="distribution" role="tab" aria-selected="false">Content Distribution</button>
        <button class="tab" type="button" data-mode="areas" role="tab" aria-selected="false">Template Areas</button>
      </div>

      <div id="alignment-demo" class="tab-panel tab-panel--active">
        <section class="demo-area" aria-label="Grid preview">
          <div id="grid-preview" class="grid-preview"></div>
        </section>

        <output id="grid-output" class="readout" aria-live="polite"></output>

        <fieldset class="control-group">
          <legend>Container Alignment (all items)</legend>
          <div class="fields-grid">
            <div class="field">
              <label for="justify-items-select">justify-items</label>
              <select id="justify-items-select">
                <option value="stretch">stretch</option>
                <option value="start">start</option>
                <option value="center" selected>center</option>
                <option value="end">end</option>
              </select>
            </div>

            <div class="field">
              <label for="align-items-select">align-items</label>
              <select id="align-items-select">
                <option value="stretch">stretch</option>
                <option value="start">start</option>
                <option value="center" selected>center</option>
                <option value="end">end</option>
              </select>
            </div>

            <div class="field">
              <label for="place-items-select">place-items <span class="shorthand-note">(shorthand)</span></label>
              <select id="place-items-select"></select>
            </div>
          </div>
        </fieldset>

        <fieldset class="control-group">
          <legend>Self-Alignment (Hero Item)</legend>
          <div class="fields-grid">
            <div class="field">
              <label for="justify-self-select">justify-self</label>
              <select id="justify-self-select">
                <option value="">(inherit)</option>
                <option value="stretch">stretch</option>
                <option value="start">start</option>
                <option value="center">center</option>
                <option value="end">end</option>
              </select>
            </div>

            <div class="field">
              <label for="align-self-select">align-self</label>
              <select id="align-self-select">
                <option value="">(inherit)</option>
                <option value="stretch">stretch</option>
                <option value="start">start</option>
                <option value="center">center</option>
                <option value="end">end</option>
              </select>
            </div>

            <div class="field">
              <label for="place-self-select">place-self <span class="shorthand-note">(shorthand)</span></label>
              <select id="place-self-select"></select>
            </div>
          </div>
        </fieldset>

        <fieldset class="control-group">
          <legend>Spanning (Hero Item)</legend>
          <div class="fields-grid">
            <div class="field">
              <label for="column-span-input">grid-column: span</label>
              <div class="range-row">
                <span class="range-edge">1</span>
                <div class="range-track">
                  <input type="range" id="column-span-input" min="1" max="3" step="1" value="1">
                  <output class="range-bubble" id="column-span-value" for="column-span-input">1</output>
                </div>
                <span class="range-edge">3</span>
              </div>
            </div>

            <div class="field">
              <label for="row-span-input">grid-row: span</label>
              <div class="range-row">
                <span class="range-edge">1</span>
                <div class="range-track">
                  <input type="range" id="row-span-input" min="1" max="3" step="1" value="1">
                  <output class="range-bubble" id="row-span-value" for="row-span-input">1</output>
                </div>
                <span class="range-edge">3</span>
              </div>
            </div>
          </div>
        </fieldset>

        <fieldset class="control-group">
          <legend>Explicit vs Implicit Grid</legend>
          <div class="fields-grid">
            <div class="field">
              <label for="item-count-input">Item count</label>
              <div class="range-row">
                <span class="range-edge">9</span>
                <div class="range-track">
                  <input type="range" id="item-count-input" min="9" max="16" step="1" value="9">
                  <output class="range-bubble" id="item-count-value" for="item-count-input">9</output>
                </div>
                <span class="range-edge">16</span>
              </div>
            </div>

            <div class="field">
              <label for="auto-flow-select">grid-auto-flow</label>
              <select id="auto-flow-select">
                <option value="row" selected>row</option>
                <option value="column">column</option>
              </select>
            </div>

            <div class="field">
              <label for="auto-track-input"><span id="auto-track-label">grid-auto-rows</span></label>
              <div class="range-row">
                <span class="range-edge">40px</span>
                <div class="range-track">
                  <input type="range" id="auto-track-input" min="40" max="140" step="10" value="60">
                  <output class="range-bubble" id="auto-track-value" for="auto-track-input">60px</output>
                </div>
                <span class="range-edge">140px</span>
              </div>
            </div>
          </div>

          <p class="tip">The first 9 items (<span class="legend-swatch explicit-swatch"></span> solid) fill the explicit 3×3 grid. Raise the item count and the rest (<span class="legend-swatch implicit-swatch"></span> dashed) overflow into browser-generated implicit tracks.</p>
        </fieldset>
      </div>

      <div id="distribution-demo" class="tab-panel">
        <section class="demo-area" aria-label="Content distribution preview">
          <div id="distribution-preview" class="distribution-preview">
            <div class="distribution-track"></div>
            <div class="distribution-track"></div>
            <div class="distribution-track"></div>
            <div class="distribution-track"></div>
            <div class="distribution-track"></div>
            <div class="distribution-track"></div>
          </div>
        </section>

        <output id="distribution-output" class="readout" aria-live="polite"></output>

        <fieldset class="control-group">
          <legend>Distribute Tracks</legend>
          <div class="fields-grid">
            <div class="field">
              <label for="justify-content-select">justify-content</label>
              <select id="justify-content-select">
                <option value="start">start</option>
                <option value="center">center</option>
                <option value="end">end</option>
                <option value="stretch">stretch</option>
                <option value="space-between" selected>space-between</option>
                <option value="space-around">space-around</option>
                <option value="space-evenly">space-evenly</option>
              </select>
            </div>

            <div class="field">
              <label for="align-content-select">align-content</label>
              <select id="align-content-select">
                <option value="start" selected>start</option>
                <option value="center">center</option>
                <option value="end">end</option>
                <option value="stretch">stretch</option>
                <option value="space-between">space-between</option>
                <option value="space-around">space-around</option>
                <option value="space-evenly">space-evenly</option>
              </select>
            </div>

            <div class="field">
              <label for="place-content-select">place-content <span class="shorthand-note">(shorthand)</span></label>
              <select id="place-content-select"></select>
            </div>
          </div>

          <p class="tip">These 6 tracks are fixed at 70×50px each inside a wider, taller container — on purpose, so there's leftover space for <code>justify-content</code>/<code>align-content</code> to distribute. Switch either to <code>stretch</code> to see it do nothing here: a fixed-size track never grows past its own size.</p>
        </fieldset>
      </div>

      <div id="areas-demo" class="tab-panel">
        <section class="demo-area" aria-label="Template areas preview">
          <div id="areas-preview" class="areas-preview"></div>
        </section>

        <output id="areas-output" class="readout" aria-live="polite"></output>

        <fieldset class="control-group">
          <legend>Layout Preset</legend>
          <div class="fields-grid">
            <div class="field">
              <label for="areas-preset-select">grid-template-areas</label>
              <select id="areas-preset-select">
                <option value="holy-grail" selected>Holy Grail</option>
                <option value="dashboard">Dashboard</option>
                <option value="sidebar-right">Sidebar Right</option>
              </select>
            </div>

            <div class="field">
              <label for="areas-gap-input">Gap</label>
              <div class="range-row">
                <span class="range-edge">0px</span>
                <div class="range-track">
                  <input type="range" id="areas-gap-input" min="0" max="24" step="2" value="8">
                  <output class="range-bubble" id="areas-gap-value" for="areas-gap-input">8px</output>
                </div>
                <span class="range-edge">24px</span>
              </div>
            </div>
          </div>

          <p class="tip">Every colored block is a real element assigned to a named region with <code>grid-area</code> — swap the preset to see the same markup reflow into a completely different shape, with zero source-order changes.</p>
        </fieldset>
      </div>
    </main>

    <script src="./index.js"></script>
  </body>
</html>

Ln , Col HTML10.9 KBUTF-8

Starting sandbox…

No console output yet.

No original version of /index.html to compare against.

Conclusion

You've now mastered the core tools that bring power, readability, and precision to CSS Grid layouts:

  • Named areas — and named lines — help you design layouts visually and semantically.
  • Item alignment ensures pixel-perfect placement in every cell.
  • justify-content / align-content distribute the whole track grid within its container, once you know that's a different job from aligning items.
  • Spanning techniques let you control flow and sizing beyond automatic placement — with an eye on keeping visual and DOM order in sync for accessibility.
  • Explicit vs. implicit grids give you confidence when your layout grows unexpectedly.
  • Subgrid lets nested grids inherit their parent's tracks, so content stays aligned across siblings.

Up next in Part 3 → Responsive CSS Grid Layouts: fr Units, minmax(), auto-fill, and auto-fit Explained, we'll unlock how to:

  • Use fr, minmax(), auto-fit, and auto-fill for fluid responsive tracks
  • Pair grid with media and container queries
  • Build robust, flexible layouts that adapt to any screen — no media query soup needed!

A lot of testing and refining went into getting these grid-area, alignment, spanning, subgrid, and accessibility examples just right, and I hope that effort shows in how clearly they click for you. Thanks for following along — hope your layouts feel a little more precise now. 📐

Changelog

  • Added a Distributing Grid Tracks section covering justify-content/align-content/place-content (with a new diagram comparing space-between, space-around, and space-evenly), plus a matching Content Distribution tab in the interactive playground
  • Added named grid lines, subgrid, and accessible-ordering sections (each with a new diagram), plus a common mistakes checklist for alignment and spanning
  • Replaced ASCII diagrams with interactive SVG illustrations, linked the Holy Grail Layout snippet, and removed CodePen links
  • Added a Template Areas mode to the interactive playground
  • Added interactive playground for alignment, spanning, and implicit tracks
  • Initial publication
This post is licensed under CC BY 4.0 by the author.

Share this post