Skip to main content

πŸŽ›οΈ Grid Container Properties

The grid container is your control panel. From it you define the tracks, name your layout regions, distribute leftover space, and steer how items flow. This lesson works through every container-level property β€” including grid-template-areas, the alignment family, and the shorthands that pack it all into one declaration.

🎯 Learning Objectives

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

  • Name grid lines and build readable layouts with grid-template-areas
  • Control the implicit grid using grid-auto-rows, grid-auto-columns, and grid-auto-flow (including dense)
  • Distinguish content alignment (justify-content/align-content) from item alignment (justify-items/align-items)
  • Use the place-* shorthands and the all-in-one grid shorthand safely
  • Assemble a responsive dashboard whose layout is redefined at each breakpoint by name

Estimated Time: 40–50 minutes  β€’  Difficulty: Intermediate

Hands-on: Build a named-area dashboard that restructures itself for tablet and mobile.

In This Lesson

The Container's Job

In the last lesson you defined tracks and sized them. Everything you set there lived on the grid container β€” the element carrying display: grid. The container is where the layout decisions live; the items simply obey. Container properties fall into four families:

graph TD A[Grid Container Properties] --> B[Define structure] A --> C[Align tracks & items] A --> D[Control the implicit grid] A --> E[Shorthands] B --> B1[grid-template-columns / rows / areas] C --> C1[justify/align-content Β· justify/align-items] D --> D1[grid-auto-rows / columns / flow] E --> E1[grid-template Β· grid]

Master these and you can express almost any two-dimensional layout, then re-express it entirely for a different screen size without touching your HTML.

Named Grid Lines

Counting lines by number works, but it gets brittle. Instead, you can name lines inside square brackets right in the template. A single line can carry several names.

.container {
  display: grid;
  grid-template-columns:
    [sidebar-start] 250px
    [sidebar-end content-start] 1fr
    [content-end];
  grid-template-rows:
    [header-start] 100px
    [header-end main-start] 1fr
    [main-end footer-start] 80px
    [footer-end];
}

Named lines are like street names instead of "third block from the edge" β€” an item placed at grid-column: content-start / content-end reads like intent, not arithmetic. You'll use these names for item placement in the next lesson.

A grid with named column and row lines A two-column, two-row grid whose lines are labelled sidebar-start, content-start, header-start and so on, with a sidebar, header and content region. [sidebar-start] [content-start] [content-end] [header-start] [main-start] [main-end] Sidebar Header Content
Figure 1 β€” Naming lines lets you place items with meaningful words instead of counting tracks.

grid-template-areas: layout you can read

The most loved Grid feature is grid-template-areas. You draw an ASCII map of your layout in CSS, then attach items to regions by name. The CSS looks like the page.

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

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

Two rules govern the map:

  • Every named area must form a rectangle β€” no L-shapes or disjointed pieces.
  • Every row string must have the same number of cells, or the whole declaration is invalid.

Use a period (.) to leave a cell deliberately empty:

grid-template-areas:
  "header  header  header"
  "sidebar content ."
  "footer  footer  footer";

πŸ“– Free named lines

Defining an area named content automatically creates the lines content-start and content-end (on both axes). So you can position other items relative to a named area without ever declaring those line names yourself.

Gap Properties

Gaps put breathing room between tracks without touching the outer edges. The modern properties are simply gap, row-gap, and column-gap:

.container {
  row-gap: 20px;      /* between rows */
  column-gap: 30px;   /* between columns */
  gap: 20px 30px;     /* shorthand: row-gap column-gap */
  gap: 20px;          /* one value applies to both */
}

⚠️ Deprecated names

The old grid-gap, grid-row-gap, and grid-column-gap were renamed to gap, row-gap, and column-gap once these properties also began working in Flexbox. Use the short names; the grid--prefixed versions only linger for legacy support.

Gaps accept any length, including responsive units, so spacing can scale with the viewport:

.container { gap: clamp(12px, 2vw, 32px); }

The Auto Properties & Dense Packing

When items land outside your explicit grid, the browser builds implicit tracks. Three properties tell it how.

grid-auto-rows / grid-auto-columns

.container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);   /* explicit */
  grid-auto-rows: minmax(100px, auto);     /* implicit rows grow with content */
}

With three explicit columns, a seventh item spills onto a new implicit row sized by grid-auto-rows. Using minmax(100px, auto) keeps a comfortable minimum while letting tall content expand.

grid-auto-flow and the dense keyword

grid-auto-flow sets the direction auto-placed items fill β€” like the reading direction of a page:

  • row (default) β€” fill left-to-right, then wrap down, like English text.
  • column β€” fill top-to-bottom, then move right.
  • dense β€” backfill earlier gaps with later, smaller items.
.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-flow: row dense;   /* backfill holes */
  gap: 10px;
}
.tall { grid-row: span 2; }
.wide { grid-column: span 2; }
Sparse flow versus dense flow Two three-column grids. The left leaves a gap after a wide item; the right uses dense packing so a smaller later item backfills that gap. grid-auto-flow: row row dense 1 2 (wide) gap 3 4 1 2 (wide) 4 3
Figure 2 β€” With dense, a later small item (4) jumps back to fill the hole the wide item left. This maximizes packing but can reorder items visually.

⚠️ Dense reorders visually, not in the DOM

dense can move an item earlier on screen while leaving it in place for keyboard and screen-reader order. Use it for decorative galleries, not for content where reading order matters.

Alignment Properties

Grid has two pairs of alignment properties, and mixing them up is the single most common Grid confusion. The trick: content aligns the whole grid of tracks; items aligns each item inside its cell.

PropertyAxisMoves…
justify-contentinline (β†’)the track grid horizontally, when it's narrower than the container
align-contentblock (↓)the track grid vertically, when it's shorter than the container
justify-itemsinline (β†’)every item within its own cell horizontally
align-itemsblock (↓)every item within its own cell vertically
/* Distribute the track grid within a larger container */
.container {
  justify-content: space-between;  /* start | end | center | space-around | space-evenly | stretch */
  align-content: center;
  place-content: center space-between; /* align-content justify-content */
}

/* Align each item inside its cell (default is stretch) */
.container {
  justify-items: center;
  align-items: center;
  place-items: center;             /* align-items justify-items */
}

πŸ’‘ Why justify-content sometimes does nothing

Content alignment only has a visible effect when the tracks don't already fill the container. If your columns are all 1fr they consume every pixel, leaving nothing to distribute β€” so justify-content appears inert. Give the tracks fixed sizes (or fewer of them) and it springs to life.

Individual items can override the container defaults with justify-self and align-self β€” covered in the next lesson on grid items.

The grid Shorthand

Two shorthands roll several properties into one. grid-template sets rows, columns, and areas together:

.page {
  display: grid;
  grid-template:
    "header  header  header" auto
    "sidebar content content" 1fr
    "footer  footer  footer" auto
    / 250px 1fr 1fr;   /* column sizes after the slash */
}

The full grid shorthand goes further, also folding in the auto properties. It has an auto-flow form too:

/* Auto-flow form: rows are 100px, two columns 1fr 2fr, flowing densely */
.container { grid: auto-flow dense 100px / 1fr 2fr; }

⚠️ The shorthand resets everything

Any grid sub-property you don't mention in the grid shorthand is reset to its initial value. That makes it dangerous to use for partial tweaks. For most real code, prefer explicit properties or the narrower grid-template β€” they're clearer and won't silently wipe a grid-auto-rows you set elsewhere.

Hands-on Exercise

πŸ‹οΈ A Dashboard That Restructures by Breakpoint

Objective: Use named areas to build a dashboard, then redefine the same names at two breakpoints so the whole layout reorganizes with no HTML changes.

Requirements

  1. Desktop: a 250px sidebar on the left; header and footer span the full width; main content beside the sidebar.
  2. Tablet (≀900px): sidebar drops below the main content.
  3. Mobile (≀600px): every region stacks in a single column.
  4. Only grid-template-areas / grid-template-columns change between breakpoints.
πŸ’‘ Hint

Assign each element to a name once (grid-area: header etc.). Then, inside each media query, restate only grid-template-columns and grid-template-areas on the container. The items follow their names automatically.

βœ… Solution
.dashboard {
  display: grid;
  gap: 16px;
  min-height: 100vh;
  grid-template-columns: 250px 1fr;
  grid-template-areas:
    "header  header"
    "sidebar main"
    "footer  footer";
}
.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.footer  { grid-area: footer; }

@media (max-width: 900px) {
  .dashboard {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "main"
      "sidebar"
      "footer";
  }
}

@media (max-width: 600px) {
  .dashboard { gap: 10px; }  /* already single column; just tighten spacing */
}

Resize the window and watch the sidebar jump from beside the content (desktop) to beneath it (tablet/mobile) β€” driven entirely by redefining the area map.

Best Practices & Pitfalls

βœ… Do

  • Start complex layouts with grid-template-areas β€” the CSS documents itself.
  • Store repeated sizes in custom properties: grid-template-columns: var(--sidebar) 1fr;
  • Use fr and minmax() for flexible tracks; save fixed px for true rails.
  • Redefine areas per breakpoint instead of rewriting markup.

⚠️ Don't

  • Don't write ragged area maps β€” every row string needs the same cell count.
  • Don't confuse *-content (moves the track grid) with *-items (moves items in cells).
  • Don't reach for the grid shorthand for partial edits β€” it resets unmentioned properties.

Summary & Quiz

πŸŽ‰ Key Takeaways

  • Named lines and grid-template-areas make placement read like intent instead of arithmetic.
  • Areas must be rectangles, and every area row must have the same cell count.
  • The auto properties (grid-auto-rows/columns/flow) govern the implicit grid; dense backfills gaps but can reorder visually.
  • Content alignment moves the whole track grid; item alignment moves items inside cells.
  • The grid shorthand is powerful but resets unmentioned sub-properties β€” prefer explicit properties.

🎯 Quick Quiz

Question 1: Which pair of properties centers each item inside its own cell (rather than moving the whole track grid)?

Question 2: Why might a valid grid-template-areas declaration be rejected by the browser?

Question 3: What is the main risk of using the all-in-one grid shorthand?

πŸ“š Further Reading

πŸš€ What's Next?

You can now shape a container completely. Next we switch sides and focus on the grid items β€” placing them by line, name, and span, and aligning each one individually with justify-self, align-self, order, and z-index.

πŸŽ‰ Container mastered!

With areas and alignment in hand, you can already build most real page shells. Let's give the items a voice of their own.