search

CSS Grid – The Complete Guide for Developers & Designers

CSS Grid is a two-dimensional layout system that lets you build grid-based structures using columns and rows – all defined in CSS, not in HTML.

Before Grid, we relied on floats, tables, and inline-block hacks. Then Flexbox arrived and solved one-dimensional alignment, but it was never designed for full page layouts.

CSS Grid fills that gap. It separates visual layout from markup order, so you can rearrange elements across screen sizes without touching the HTML. I’ve been using it in production for years, and it still saves me hours on every project.

You might want to skip straight to creating your first grid.

What is CSS Grid?

Grid is the first CSS module built specifically for two-dimensional layouts. It handles both columns and rows at the same time, something Flexbox was never designed to do.

The key concept: you describe the layout structure at the CSS level, then let elements flow into the grid. Redefine the grid inside a Media Query and the layout adapts without any markup changes.

Because each element occupies a defined area, you don’t get the overlap or overflow problems that plagued float-based designs.

Terminology – CSS Grid

Before diving in, let’s cover the core terminology. These terms sound similar at first, but understanding the difference between them is essential for everything that follows.

Don’t worry if these terms feel abstract right now. Each one will click once you start building grids in the sections that follow.

1. Grid Container

The Grid Container is the parent element that holds the grid layout. You create it by adding display: grid or display: inline-grid to an element.

In the example below, the outer bordered area is the Grid Container:

1
2
3
4

Once an element is defined as a Grid Container, you can use various properties to control its layout. Note that, just like Flexbox, CSS Grid respects the page direction. The example above is LTR. In an RTL context, it would look like this:

1
2
3
4

2. Grid Line

Grid Lines are the dividing lines that form the structure of the grid. They run both vertically and horizontally, and are numbered starting from 1.

In a 3-column, 3-row grid there are 4 vertical lines (column lines) and 4 horizontal lines (row lines). You reference these numbers when placing items with grid-column and grid-row.

The numbers along the top are column lines (left to right) and the numbers along the left are row lines (top to bottom):

1 2 3 4 1 2 3 4
1
2
3
4
5
6
7
8
9

3. Grid Cell

A Grid Cell is the smallest unit in the grid – a single slot at the intersection of one row and one column. Think of it like one square on a chessboard. In a 3×3 grid, you have 9 cells.

A Grid Cell is not an HTML element. It’s a conceptual space defined by the grid structure.

The highlighted cell below shows a single Grid Cell:

1
2
3
4
5
6

4. Grid Track

A Grid Track is the space between two adjacent Grid Lines. You control track sizes with grid-template-columns and grid-template-rows.

  • Horizontal tracks (between row lines) are called row tracks.
  • Vertical tracks (between column lines) are called column tracks.

The highlighted row below represents a Grid Track:

1
2
3
4
5
6

5. Grid Area

A Grid Area is a rectangular region that spans one or more Grid Cells. You can define areas using grid-template-areas.

The highlighted cells below form a single Grid Area:

1
2
3
4
5
6

6. Grid Item

Grid Items are the actual HTML elements inside the grid – the direct children of the Grid Container. By default, each item occupies one cell, but items can span multiple cells, tracks, or entire areas.

Grid Item
Grid Item
Grid Item
Grid Item

With these terms covered, let’s build our first grid.

Creating your first Grid

The two core components are the Grid Container (the parent) and the Grid Items (the children inside it).

Here is the markup for a container with six items:

<div class="wrapper">
  <div>1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <div>6</div>
</div>

To turn this into a Grid, add display: grid:

.wrapper {
    display: grid;
}

The result:

Grid Item
Grid Item
Grid Item
Grid Item
Grid Item
Grid Item

Right now it looks like six stacked elements because we haven’t defined any columns or rows yet. Let’s fix that.


Creating Columns and Rows in the Grid

To make the Grid two-dimensional, we define columns and rows using grid-template-columns and grid-template-rows:

.wrapper {
    display: grid;
    grid-template-columns: 33% 33% 33%;
    grid-template-rows: 60px 60px;
}

Three values for grid-template-columns means three columns. Two values for grid-template-rows means two rows.

Grid Item
Grid Item
Grid Item
Grid Item
Grid Item
Grid Item

To verify you understand the connection between values and layout, take a look at this variation:

.wrapper {
    display: grid;
    grid-template-columns: 20% 60% 20%;
    grid-template-rows: 80px 40px;
}
1
2
3
4
5
6

You’re not limited to percentages. You can use px, viewport units, calc(), or the fr unit which we’ll cover later.


Positioning Elements in the Grid

Now let’s position items precisely. Create a 3×3 grid:

.wrapper {
    display: grid;
    grid-template-columns: 33% 33% 33%;
    grid-template-rows: 60px 60px 60px;
}

1
2
3
4
5
6

We defined three rows but only see two filled rows because there are only six items. The third row exists but remains empty.
To change an item’s position or size, target it and use grid-column-start / grid-column-end:

.wrapper > div:nth-child(1) {
    grid-column-start: 1;
    grid-column-end: 4;
}

The first item starts at Grid Line 1 and ends at Grid Line 4, spanning the entire row:

1
2
3
4
5
6

If you remember the Grid Lines example from earlier, a 3-column grid has 4 column lines and a 3-row grid has 4 row lines. Line 4 is the right edge of the last column:

1 2 3 4 1 2 3 4
1
2
3
4
5
6
7
8
9

The same concept applies to rows with grid-row-start / grid-row-end. Here’s a more complex example:

.wrapper > div:nth-child(1) {
    grid-column-start: 1;
    grid-column-end: 3;
}

.wrapper > div:nth-child(3) {
    grid-row-start: 2;
    grid-row-end: 4;
}

.wrapper > div:nth-child(4) {
    grid-column-start: 2;
    grid-column-end: 4;
}
1
2
3
4
5
6

There’s a shorter syntax using grid-column and grid-row:

.wrapper > div:nth-child(1) {
    grid-column: 1 / 3;
}

.wrapper > div:nth-child(3) {
    grid-row: 2 / 4;
}

.wrapper > div:nth-child(4) {
    grid-column: 2 / 4;
}

You can also use span to define how many tracks an item should stretch across:

.wrapper > div:nth-child(1) {
    grid-column: 1 / span 2;
}

.wrapper > div:nth-child(3) {
    grid-row: 2 / span 2;
}

.wrapper > div:nth-child(4) {
    grid-column: 2 / span 2;
}

Named Grid Lines

Instead of referencing lines by number, you can name them when defining the grid:

.wrapper {
    display: grid;
    grid-template-columns: [sidebar-start] 200px [sidebar-end content-start] 1fr [content-end];
    grid-template-rows: [header-start] 80px [header-end main-start] 1fr [main-end];
}

Then position items using those names:

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

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

Named lines make your CSS more readable, especially in complex layouts where line numbers become hard to track.


Aligning Elements in the Grid

CSS Grid gives you six alignment properties. They’re part of the CSS Box Alignment Module and work in both Grid and Flexbox:

  • justify-items / align-items
  • justify-content / align-content
  • justify-self / align-self

Container-Level Alignment

For the following demos, we use a grid with three 100px columns and 50x50px items, so there’s visible space around each item:

.my-container {
    display: grid;
    grid-template-columns: 100px 100px 100px;
    width: 100%;
    height: 200px;
}

.item {
    width: 50px;
    height: 50px;
}
1
2
3
4
5
6

The items don’t fill the cells – they sit at the top-left by default (top-right in RTL).

The justify-items property

Aligns all Grid Items along the row axis (horizontal). Try the different values:

1
2
3
4
5
6

The align-items property

Aligns all Grid Items along the column axis (vertical):

1
2
3
4
5
6

The justify-content property

When the total grid is smaller than the container, justify-content positions the entire grid along the row axis:

1
2
3
4
5
6

Here’s the same property on a grid with more items so the effect is clearer:

1
2
3
4
5
6
7
8
9
10
11
12

The align-content property

Same concept but along the column axis (vertical):

1
2
3
4
5
6

Item-Level Alignment

The justify-self and align-self properties work like justify-items and align-items, but apply to individual Grid Items instead of all items at once.

The justify-self property

Aligns a single item horizontally. Here, item 3 has justify-self: end:

<div class="my-container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item" style="justify-self: end;">3</div>
  <div class="item">4</div>
  <div class="item">5</div>
  <div class="item">6</div>
</div>
1
2
3
4
5
6

The align-self property

Aligns a single item vertically. Here, item 3 has align-self: end:

<div class="my-container">
  <div class="item">1</div>
  <div class="item">2</div>
  <div class="item" style="align-self: end;">3</div>
  <div class="item">4</div>
  <div class="item">5</div>
  <div class="item">6</div>
</div>
1
2
3
4
5
6

Shorthand Properties

CSS provides three shorthands that combine the justify and align properties into one line:

/* place-items = align-items / justify-items */
.container {
    place-items: center;
}

/* place-content = align-content / justify-content */
.container {
    place-content: space-between center;
}

/* place-self = align-self / justify-self */
.item {
    place-self: end center;
}

When you provide a single value, it applies to both axes. Two values set the block (vertical) axis first, then the inline (horizontal) axis.

Combining Alignment Properties

You can combine multiple alignment properties to achieve precise layouts:

.my-container {
    justify-content: space-evenly;
    justify-items: center;
    align-content: space-evenly;
    align-items: center;
}
1
2
3
4
5
6

Notice how item 3 overrides align-items: center with its own align-self: end.


Implicit Grids and grid-auto-flow

So far we’ve been defining the grid structure explicitly with grid-template-columns and grid-template-rows – that’s the explicit grid. But what happens when there are more items than cells?

The browser creates additional tracks automatically. This is called the implicit grid. You control the size of those auto-created tracks with grid-auto-rows and grid-auto-columns:

.wrapper {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: 70px;
    grid-auto-rows: 50px;
}

Here we defined one explicit row of 70px. Any items that overflow into additional rows will get 50px height from grid-auto-rows.

The grid-auto-flow property

By default, Grid places items row by row (grid-auto-flow: row). You can change this to fill columns first, or use the dense keyword to backfill gaps left by larger items.

Try the different values:

1 (span 2)
2 (span 2)
3
4
5

Items 1 and 2 both span two columns. In row mode, item 2 can’t fit next to item 1 so it drops to the next row, leaving an empty cell in column 3. Switch to dense and watch item 3 backfill that gap.

With column, items flow top-to-bottom instead of left-to-right. Try column dense to see both effects combined.

With dense, visual order may differ from source order. This can affect keyboard navigation and accessibility, so use it carefully.


The grid-area Property

The grid-area property is a shorthand for grid-row-start, grid-column-start, grid-row-end, and grid-column-end:

grid-area: <row-start> / <column-start> / <row-end> / <column-end>

Check out the grid-area and grid-template-areas guide for more examples.

Here’s a grid with 11 items where item 5 uses grid-area: 1 / 2 / 5 / 7 to span a large region:

.wrapper {
    display: grid;
}

.wrapper > div:nth-child(5) {
    grid-area: 1 / 2 / 5 / 7;
}
1
2
3
4
5
6
7
8
9
10
11

Overlapping Grid Items

Unlike Flexbox, Grid allows items to occupy the same cells. This creates overlapping elements that you can control with z-index:

.item-a {
    grid-column: 1 / 4;
    grid-row: 1 / 3;
    z-index: 1;
}

.item-b {
    grid-column: 2 / 5;
    grid-row: 2 / 4;
    z-index: 2;
}
A (z-index: 1)
B (z-index: 2)

This technique is useful for layered layouts – hero sections with overlapping text, card designs with badges, or creative magazine-style grids.


The gap Property

The gap property sets spacing between grid tracks. It’s a shorthand for row-gap and column-gap.

The older grid-gap, grid-row-gap, and grid-column-gap names still work but are deprecated. Use the unprefixed versions: gap, row-gap, column-gap.

The first value is the row gap, the second is the column gap:

.wrapper {
    display: grid;
    gap: 30px 10px;
}
1
2
3
4
5
6

There’s a separate post covering the gap property in both Grid and Flexbox.


The fr Unit (Fractional Unit)

The fr unit distributes available space proportionally. It eliminates the math problems that come with percentages.

Consider this: if you use grid-template-columns: 33% 33% 33% with gap: 10px, the total width becomes 100% + 20px, causing overflow.

With fr units, the browser handles the math for you:

.wrapper {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
    grid-auto-rows: 80px;
}
1fr
1fr
1fr
1fr
1fr
1fr

Use the repeat() function for cleaner code: grid-template-columns: repeat(6, 1fr).

You can mix fr with fixed units. Here, two 100px columns and one flexible column:

.wrapper {
    display: grid;
    grid-template-columns: 100px 100px 1fr;
    grid-auto-rows: 80px;
}
100px
100px
1fr

Fractional values don’t have to be whole numbers:

.wrapper {
    display: grid;
    grid-template-columns: 1.5fr 3fr 4.5fr;
    grid-auto-rows: 80px;
}
1.5fr
3fr
4.5fr

For more on fractional units, see CSS Grid fr unit explained.


Responsive Grids with auto-fill, auto-fit, and minmax()

One of the most powerful CSS Grid patterns creates fully responsive layouts without any Media Queries:

.wrapper {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 10px;
}

The minmax(150px, 1fr) function sets a minimum of 150px and a maximum of 1fr per column. Combined with auto-fit, the browser creates as many columns as fit and expands them to fill remaining space.

The difference between auto-fill and auto-fit matters when there are fewer items than available space. Try switching between them:

1
2
3

auto-fit collapses empty tracks and stretches existing items to fill the row. auto-fill keeps the empty tracks, so items stay at their minimum width.

For a deeper look, see the posts on the minmax() function and responsive Grid layouts without Media Queries.


Creating a Website Structure Using CSS Grid

Let’s put it all together and build a full page layout using grid-template-areas:

<div class="wrapper">
    <header>header</header>
    <nav>nav</nav>
    <section>section</section>
    <aside>aside</aside>
    <footer>footer</footer>
</div>
.wrapper {
    display: grid;
    grid-template-areas:
        "header header header"
        "nav section aside"
        "footer footer footer";
    grid-template-rows: 80px 1fr 50px;
    grid-template-columns: 15% 1fr 15%;
    gap: 4px;
    height: 360px;
}

header  { grid-area: header; }
nav     { grid-area: nav; }
section { grid-area: section; }
aside   { grid-area: aside; }
footer  { grid-area: footer; }
header
section
footer

Look at how little code that took. The grid-template shorthand can combine rows, columns, and areas into a single property if you prefer even more compact code.

The Grid Template Areas Property

Each string in grid-template-areas represents one row. You name items with grid-area and reference those names in the template. Use a period (.) for unnamed/empty cells.

  • "header header header" – header spans all three columns
  • "nav section aside" – three columns with different areas
  • "footer footer footer" – footer spans the full width

Want the nav to span the full height? Change one CSS property:

.wrapper {
    grid-template-areas:
        "nav header header"
        "nav section aside"
        "nav footer footer";
}
header
section
footer

What about Responsiveness?

The layout already stretches and shrinks with the container. For a completely different mobile layout, add a Media Query that only changes grid-template-areas:

@media (max-width: 768px) {
    .wrapper {
        grid-template-areas:
            "header header header"
            "nav nav nav"
            "section section section"
            "aside aside aside"
            "footer footer footer";
        grid-template-rows: 50px 30px 1fr 60px 30px;
    }
}
header
section
footer

Restructuring the entire page layout by changing a single CSS property is one of Grid’s best features. You can also skip Media Queries entirely using auto-fit and minmax() as shown earlier in this guide.


Subgrid

Subgrid lets nested grids inherit their parent’s track sizes. It has 97% global browser support as of 2026 (Chrome 117+, Firefox 71+, Safari 16+) and is production-ready.

The problem it solves: when you have a grid of cards, each card’s internal elements (title, description, footer) can’t align across cards because each card has its own independent grid. With Subgrid, the child grid adopts the parent’s row tracks:

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

.card {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
    gap: 8px;
}

Each card spans 3 parent rows and uses grid-template-rows: subgrid to align its content with the other cards:

Short Title

Brief description.

A Much Longer Card Title That Wraps

This card has more description text that takes up more vertical space than the others.

Medium Title

This has a medium-length description.

Notice how all three card titles, descriptions, and footers align horizontally, regardless of their content length. Without Subgrid, each card’s rows would be independent and the alignment would break.

You can also use grid-template-columns: subgrid for column inheritance. This is especially useful for form layouts where labels and inputs need to align across rows.

CSS Masonry layout has been renamed to display: grid-lanes. Safari Technology Preview supports it by default, while Chrome and Firefox still require a flag. It’s not production-ready yet, but it’s getting close.


CSS Grid vs Flexbox

Flexbox is designed for one-dimensional layouts (a row or a column). Grid is for two-dimensional layouts (rows and columns together).

One-dimensional layout – use Flexbox:

Element
Element
Element

Two-dimensional layout – use Grid:

header
section
footer

In practice, you should use both. Grid handles the page structure, Flexbox handles alignment within components like navigation bars, card contents, or button groups.

If Grid performance concerns you on large layouts, take a look at CSS Grid’s impact on web performance.


Browser Support

CSS Grid has 98% global browser support. Every modern browser has fully supported it since 2017. Use it confidently in production without fallbacks.


FAQs

Common questions about CSS Grid:

What is the difference between CSS Grid and Flexbox?
Flexbox is designed for one-dimensional layouts - either a row or a column. CSS Grid handles two-dimensional layouts where you control both rows and columns at the same time. Use Flexbox for component-level alignment (navbars, button groups) and Grid for page-level structure. You can also nest Flexbox inside Grid items.
Can I use CSS Grid in production?
Yes. CSS Grid has 98% global browser support and has been fully supported in all modern browsers since 2017. You can use it without fallbacks in any production project.
What does fr mean in CSS Grid?
The fr unit stands for "fractional unit." It distributes available space proportionally between grid tracks. For example, grid-template-columns: 1fr 2fr creates two columns where the second is twice as wide as the first. Unlike percentages, fr units automatically account for gaps and fixed-size tracks.
How do I make a CSS Grid layout responsive without Media Queries?
Use repeat(auto-fit, minmax(min, 1fr)) for your column template. The browser will create as many columns as fit within the container, each at least min wide, and stretch them to fill remaining space. For example, grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) creates a fully responsive grid that adapts to any screen width with zero Media Queries.
What is CSS Subgrid?
Subgrid lets a nested grid inherit its parent's track sizes using grid-template-rows: subgrid or grid-template-columns: subgrid. This is useful when you need child elements across multiple grid items to align with each other, like card titles and footers lining up across a row of cards. It has 97% browser support as of 2026.
Can Grid items overlap?
Yes. Unlike Flexbox, CSS Grid allows multiple items to occupy the same cells. You control which item appears on top using the z-index property. This is useful for layered designs like hero sections with overlapping text, badges on cards, or creative magazine-style layouts.

Summary

This guide covered the core of CSS Grid: containers, items, tracks, areas, alignment, positioning, the gap and fr unit, grid-template-areas for page layouts, implicit grids, auto-flow, subgrid, and overlapping items.

Grid cuts layout code significantly compared to older techniques. To go deeper, check out the dedicated posts linked throughout this guide, or take a look at CSS Variables – another feature you’ll want in your toolkit.

If you need components to adapt their layout based on available space rather than viewport width, CSS Container Queries pair perfectly with Grid.

Join the Discussion
1 Comments  ]
  • Brian 13 May 2025, 17:16

    What a wonderful guide 🙂 Thanks!

Leave a Comment

To add code, use the buttons below. For instance, click the PHP button to insert PHP code within the shortcode. If you notice any typos, please let us know!

Savvy WordPress Development official logo