CSS Grid Container Properties

CSS Grid is a two-dimensional layout system: unlike Flexbox, which lays items out along a single row or column, Grid lets you define both rows and columns at the same time and place items precisely into that structure. Every Grid layout starts with a grid container — the parent element you switch into grid mode — and the properties you set on that container control how many tracks exist, how big they are, how much space sits between them, and how leftover space is distributed. This lesson covers every major container-level property in depth.

Overview: How CSS Grid Containers Work

When you set display: grid (or display: inline-grid) on an element, two things happen. First, that element becomes a grid container: its rendering context switches from normal flow to grid layout. Second, every direct child of that element automatically becomes a grid item — you don’t need to opt items in individually, the way you sometimes do with positioned elements.

The browser’s rendering engine then builds an internal structure called the grid, made of horizontal and vertical grid lines that divide the container into cells. You control the size of the columns and rows between those lines with grid-template-columns and grid-template-rows. Anything you explicitly define this way is called the explicit grid. If you place more items than fit into that explicit structure, the browser generates extra rows or columns automatically — this is the implicit grid, and its track sizing is controlled separately by grid-auto-rows, grid-auto-columns, and grid-auto-flow.

Grid track sizing supports a special unit, the fr (fraction) unit, which represents a share of the leftover space in the container after fixed-size tracks, gaps, and content-based sizes have been subtracted. This is what makes Grid so powerful for responsive layouts without media queries: 1fr 2fr divides remaining space into three parts, giving the second column twice as much as the first.

Once the tracks exist, the container also controls two independent axes of alignment: how items are positioned within their cells (justify-items / align-items), and how the whole grid is positioned within the container if the tracks don’t fill it completely (justify-content / align-content). This mirrors Flexbox’s alignment model but applies to both dimensions instead of one.

Syntax

The general shape of a grid container ruleset looks like this:

.container {
  display: grid;
  grid-template-columns: track-size ...;
  grid-template-rows: track-size ...;
  grid-template-areas: "name name" "name name";
  gap: row-gap column-gap;
  justify-items: start | end | center | stretch;
  align-items: start | end | center | stretch;
  justify-content: start | end | center | space-between | space-around | space-evenly;
  align-content: start | end | center | space-between | space-around | space-evenly;
  grid-auto-flow: row | column | dense;
  grid-auto-rows: track-size;
  grid-auto-columns: track-size;
}

The table below summarizes the key container properties:

Property Purpose
display Turns the element into a grid container (grid or inline-grid)
grid-template-columns Defines the number and size of explicit column tracks
grid-template-rows Defines the number and size of explicit row tracks
grid-template-areas Names regions of the grid using quoted strings, for placing items by name
gap (row-gap/column-gap) Space between tracks, not added at the outer edges
justify-items / align-items Default alignment of items inside their own cell, inline and block axis
justify-content / align-content Alignment of the whole track set when it’s smaller than the container
grid-auto-flow Direction and packing algorithm for auto-placed items
grid-auto-rows / grid-auto-columns Size of implicitly generated tracks

Examples

Example 1: A simple responsive gallery grid. Applied to a container with six child <div> elements:

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

.gallery > div {
  background-color: #e2e8f0;
  padding: 20px;
  border-radius: 8px;
  text-align: center;
}

Result: The six items arrange into a grid of exactly three equal-width columns; since there are six items, they wrap into two rows automatically. Each cell has a light gray background with rounded corners, and there is a consistent 16px gap both between columns and between the two rows.

The repeat(3, 1fr) function is shorthand for writing 1fr 1fr 1fr. Because all three tracks share the fr unit equally, the container’s width (minus the two 16px gaps) is divided into three identical shares. Rows are not explicitly defined, so the browser creates two implicit rows sized to fit their content (the default grid-auto-rows: auto behavior).

Example 2: A named-area page layout. Applied to a container with .site-header, .site-sidebar, .site-main, and .site-footer children:

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

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

Result: The header spans the full width across the top row. Below it, a fixed 200px sidebar sits on the left while the main content fills the rest of the width to its right. The footer spans the full width again at the bottom. The whole layout stretches to fill at least the viewport height, with 12px of spacing between every region.

This is the power of grid-template-areas: it lets you describe a layout visually, as a grid of names, instead of calculating line numbers. Each quoted string represents one row, and repeating a name across adjacent cells (like header header) makes that item span both columns. Every row string must contain the same number of cell names, and each named region must form a rectangle.

Example 3: Responsive cards with auto-fill and alignment.

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  grid-auto-rows: minmax(120px, auto);
  grid-auto-flow: dense;
  justify-content: center;
  align-content: start;
  gap: clamp(8px, 2vw, 24px);
}

Result: As many 180px-minimum columns as fit in the container are created, each stretching to share any remaining space equally (up to 1fr). As the viewport narrows, columns drop away and cards reflow onto new rows, with no media query required. Rows are at least 120px tall but grow with content. The gap between cards scales smoothly between 8px and 24px depending on viewport width, and if the tracks don’t fill the container’s width, the whole set of columns is centered horizontally.

Here auto-fill tells the browser to fit as many tracks as possible into the available space, even leaving some empty if the container is wide; minmax(180px, 1fr) gives each of those tracks a floor and a ceiling. grid-auto-flow: dense tells the auto-placement algorithm to backfill earlier empty cells when a later, smaller item can fit there, reducing gaps left by irregularly sized items.

How It Works Step by Step

When the rendering engine lays out a grid container, it roughly follows these stages:

1. Resolve the explicit grid. The engine reads grid-template-columns, grid-template-rows, and grid-template-areas to build the initial set of named lines and tracks.

2. Place items. Items with explicit grid-column/grid-row/grid-area values are placed first. Remaining items are auto-placed by an internal "placement cursor" that walks row by row (or column by column, if grid-auto-flow: column is set), filling the next available cell.

3. Grow the implicit grid. If an item’s position falls outside the explicit tracks, new implicit tracks are generated, sized according to grid-auto-rows / grid-auto-columns (default auto, meaning "size to content").

4. Size the tracks. This is the most complex step. The engine first gives every track its base size from fixed lengths and content minimums, then distributes any remaining free space to tracks with an fr unit, proportionally to their fraction — similar in spirit to how flex-grow distributes space in Flexbox.

5. Position the grid within the container. If the sized tracks don’t consume the full container size, justify-content/align-content determine where the whole block of tracks sits, and how any leftover space is distributed between or around them.

6. Position each item within its cell. Finally, justify-items/align-items (or a per-item justify-self/align-self) determine whether the item stretches to fill its cell or hugs one edge or the center.

Common Mistakes

Mistake 1: Setting grid properties without turning on grid mode. This CSS is perfectly valid and will parse without error, but it does nothing useful:

.wrapper {
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

Because display: grid is missing, the element stays in normal block flow. grid-template-columns and gap are simply ignored, and the children stack vertically as ordinary block boxes. The fix is to always pair grid-template properties with display: grid:

.wrapper {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

Mistake 2: Separating track sizes with commas. Track lists in grid-template-columns/grid-template-rows are space-separated, not comma-separated (commas are only used inside functions like repeat() or minmax()). This declaration is invalid and will be dropped by the browser:

.wrapper {
  display: grid;
  grid-template-columns: 1fr, 1fr, 1fr;
}

The correct syntax removes the commas between tracks:

.wrapper {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}

Best Practices

  • Use the fr unit for flexible tracks instead of percentages — it accounts for gaps automatically, while percentage-based tracks can overflow once gap is added.
  • Reach for repeat(auto-fill, minmax(...)) or repeat(auto-fit, minmax(...)) for responsive card/grid layouts before writing a media query — it’s often all you need.
  • Use grid-template-areas for page-level layouts; the visual, name-based syntax is far easier to read and maintain than juggling line numbers.
  • Prefer gap over margins on grid items for spacing between cells — it never adds space at the outer edge and doesn’t require negative-margin workarounds.
  • Only define explicit rows when the number of rows matters to your design; otherwise let the implicit grid and grid-auto-rows handle overflow content.
  • Use the place-items and place-content shorthands (which set both axes at once) when you want the same alignment on both dimensions.

Practice Exercises

Exercise 1: Build a container with four equal-width columns and a 20px gap. Add a fifth child element and predict, then verify, which row it lands on.

Exercise 2: Using grid-template-areas, lay out a three-item layout with a full-width header, a full-width footer, and a single content area between them. Give the header and footer a different background color from the content area to visually confirm the regions.

Exercise 3: Create a card grid using repeat(auto-fit, minmax(150px, 1fr)) and resize your browser window (or the preview pane) to observe how the number of columns changes without any media query.

Summary

  • display: grid (or inline-grid) turns an element into a grid container and its direct children into grid items.
  • grid-template-columns/grid-template-rows define the explicit grid; the fr unit distributes leftover space proportionally.
  • grid-template-areas lets you name regions of the grid and place items by name instead of by line number.
  • gap controls spacing between tracks without affecting the container’s outer edges.
  • justify-items/align-items position items within their own cell; justify-content/align-content position the whole track set within the container.
  • Items that fall outside the explicit grid generate implicit tracks, sized by grid-auto-rows/grid-auto-columns and placed according to grid-auto-flow.
  • Grid properties only take effect once display: grid is set, and track lists must be space-separated, not comma-separated.