CSSintermediate

CSS Grid Complete Guide

Master CSS Grid: template columns, rows, areas, auto-fit, auto-fill, subgrid, and responsive grid patterns. Everything you need to build two-dimensional layouts.

18 min readยทPublished Mar 28, 2026
cssgridlayoutresponsive

What Is CSS Grid?

CSS Grid is a two-dimensional layout system. Unlike flexbox (one axis at a time), Grid handles rows AND columns simultaneously. It lets you define a layout structure on the container, then place items into that structure precisely.

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

That gives you a three-column, three-row layout. The middle column and row expand to fill available space.

+----------+------------------+----------+
|  200px   |       1fr        |  200px   |  <- auto (content height)
+----------+------------------+----------+
|          |                  |          |
|  200px   |       1fr        |  200px   |  <- 1fr (fills remaining)
|          |                  |          |
+----------+------------------+----------+
|  200px   |       1fr        |  200px   |  <- auto (content height)
+----------+------------------+----------+

Grid vs Flexbox: The Short Version

  • Flexbox: one direction at a time (row OR column).
  • Grid: both directions at once (rows AND columns).

Flexbox is content-driven โ€” items determine their own sizes. Grid is layout-driven โ€” the container defines the structure and items fill it.

Grid Container Setup

display: grid vs display: inline-grid

/* Block-level grid โ€” takes full width */
.container {
  display: grid;
}

/* Inline-level grid โ€” shrinks to content */
.container-inline {
  display: inline-grid;
}

In practice, display: grid is used almost exclusively.

grid-template-columns

Defines the number and size of columns.

.grid {
  display: grid;

  /* Fixed sizes */
  grid-template-columns: 100px 200px 100px;

  /* Fractional units โ€” divide available space */
  grid-template-columns: 1fr 2fr 1fr;

  /* Mixed */
  grid-template-columns: 250px 1fr 250px;

  /* Repeat shorthand */
  grid-template-columns: repeat(4, 1fr);

  /* Auto-sized */
  grid-template-columns: auto 1fr auto;

  /* Min-max */
  grid-template-columns: minmax(200px, 1fr) 2fr minmax(200px, 1fr);
}

The fr unit divides the remaining space after fixed-size tracks are allocated:

Container: 1000px
Columns:   200px 1fr 2fr

1. Fixed track: 200px
2. Remaining: 1000 - 200 = 800px
3. 1fr = 800 / 3 = 267px
4. 2fr = 800 * 2/3 = 533px

Result: [200px][267px][533px]

grid-template-rows

Same syntax as columns, but for rows.

.grid {
  display: grid;
  grid-template-rows: auto 1fr auto;     /* header, content, footer */
  grid-template-rows: 60px 1fr 40px;     /* fixed header/footer */
  grid-template-rows: repeat(3, 100px);  /* three 100px rows */
}

Rows not explicitly defined will be implicit rows โ€” sized by grid-auto-rows.

grid-template (Shorthand)

Combines rows and columns:

.grid {
  /* rows / columns */
  grid-template: auto 1fr auto / 200px 1fr 200px;
}

gap (row-gap and column-gap)

.grid {
  display: grid;
  gap: 1rem;              /* equal row and column gap */
  row-gap: 1rem;          /* gap between rows */
  column-gap: 2rem;       /* gap between columns */
  gap: 1rem 2rem;         /* row-gap column-gap */
}

gap does not add spacing on the outside edges โ€” only between tracks.

Named Grid Lines

You can name grid lines for clearer placement:

.grid {
  display: grid;
  grid-template-columns:
    [sidebar-start] 250px
    [sidebar-end content-start] 1fr
    [content-end aside-start] 250px
    [aside-end];
}

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

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

Grid Item Placement

grid-column and grid-row

Items are placed using grid line numbers. Lines are numbered starting at 1.

.item {
  grid-column: 1 / 3;    /* spans from line 1 to line 3 (two columns) */
  grid-row: 1 / 2;       /* spans from line 1 to line 2 (one row) */
}
Line:  1       2       3       4
       |       |       |       |
       | Col 1 | Col 2 | Col 3 |
       |       |       |       |

grid-column: 1 / 3 spans:
       |===============|       |
       | Col 1 + Col 2 |       |

Shorthand with span:

.item {
  grid-column: 1 / span 2;   /* start at line 1, span 2 columns */
  grid-column: span 3;        /* span 3 columns from wherever placed */
  grid-row: 2 / span 2;       /* start at row line 2, span 2 rows */
}

grid-column-start / grid-column-end

Explicit start and end (same as the shorthand above):

.item {
  grid-column-start: 1;
  grid-column-end: 3;
  grid-row-start: 1;
  grid-row-end: 4;
}

grid-area (Shorthand)

Combines row and column placement:

.item {
  /* row-start / column-start / row-end / column-end */
  grid-area: 1 / 1 / 3 / 4;
}

Or used with named areas (covered next).

Negative Line Numbers

Count from the end using negative numbers:

.full-width {
  grid-column: 1 / -1;  /* spans from first to last line */
}

This is useful for items that should span the entire grid regardless of column count.

Grid Template Areas

Named areas make complex layouts readable.

.layout {
  display: grid;
  grid-template-columns: 200px 1fr 200px;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header  header  header"
    "sidebar content aside"
    "footer  footer  footer";
  gap: 1rem;
  min-height: 100vh;
}

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

Rules for template areas:

  • Each string is a row.
  • Names must form rectangles (no L-shapes or T-shapes).
  • Use . for empty cells.
  • Area names must be valid CSS identifiers.
.grid {
  grid-template-areas:
    "header header  header"
    "nav    content ."        /* dot = empty cell */
    "nav    footer  footer";
}

Responsive Template Areas

Template areas shine in responsive design โ€” redefine the layout per breakpoint:

.layout {
  display: grid;
  grid-template-areas:
    "header"
    "content"
    "sidebar"
    "footer";
}

@media (min-width: 768px) {
  .layout {
    grid-template-columns: 250px 1fr;
    grid-template-areas:
      "header  header"
      "sidebar content"
      "footer  footer";
  }
}

@media (min-width: 1200px) {
  .layout {
    grid-template-columns: 250px 1fr 200px;
    grid-template-areas:
      "header  header  header"
      "sidebar content aside"
      "footer  footer  footer";
  }
}

The HTML structure stays the same. Only the CSS changes.

Implicit Grid & Auto Placement

grid-auto-rows and grid-auto-columns

When items land outside the explicit grid (more items than defined rows), they create implicit tracks. Control their size:

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 100px 100px;    /* only 2 explicit rows */
  grid-auto-rows: 150px;               /* implicit rows = 150px */
  grid-auto-rows: minmax(100px, auto); /* at least 100px, grow to fit */
}

grid-auto-flow

Controls how auto-placed items fill the grid:

.grid {
  grid-auto-flow: row;         /* default: fill rows left to right */
  grid-auto-flow: column;      /* fill columns top to bottom */
  grid-auto-flow: dense;       /* fill gaps (may reorder items) */
  grid-auto-flow: row dense;   /* fill rows, backfill gaps */
}

dense packing reorders items to fill holes left by larger items:

Without dense:           With dense:
[A  A][B ][   ]         [A  A][B ][D ]
[C  C  C  ][   ]        [C  C  C  ][E ]
[D ][E ][   ]           (gaps filled)

Warning: dense changes visual order. Like flexbox order, screen readers still follow DOM order.

repeat(), minmax(), auto-fit, auto-fill

repeat()

Shorthand for repeating track definitions:

/* These are equivalent */
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-template-columns: repeat(4, 1fr);

/* Repeat patterns */
grid-template-columns: repeat(3, 100px 1fr);
/* produces: 100px 1fr 100px 1fr 100px 1fr */

minmax()

Defines a size range for a track:

grid-template-columns: minmax(200px, 1fr) 2fr;
/* first column: at least 200px, at most 1fr */

grid-template-rows: minmax(100px, auto);
/* row: at least 100px, grows to fit content */

auto-fit vs auto-fill

Both create as many columns as fit, but behave differently when there are fewer items than columns:

/* auto-fill: creates empty tracks for unused space */
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));

/* auto-fit: collapses empty tracks, items stretch */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
Container: 1000px, 3 items, minmax(200px, 1fr)

auto-fill (5 columns created, 2 empty):
[Item 1][Item 2][Item 3][      ][      ]
  200px   200px   200px  200px   200px

auto-fit (3 columns, items stretch):
[  Item 1  ][  Item 2  ][  Item 3  ]
   333px       333px       333px

Use auto-fit when you want items to stretch and fill the container. Use auto-fill when you want consistent column widths regardless of item count.

The responsive grid pattern (no media queries):

.responsive-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1rem;
}

This creates a grid where:

  • Columns are at least 250px wide.
  • As many columns as fit in the container.
  • Items stretch to fill remaining space.
  • Automatically reflows on resize. No media queries needed.

Alignment in Grid

Grid has the same alignment properties as flexbox, plus more.

Container Alignment (All Items)

.grid {
  display: grid;
  grid-template-columns: repeat(3, 200px);

  /* Align all items within their cells */
  justify-items: start;     /* horizontal: start of cell */
  justify-items: end;       /* horizontal: end of cell */
  justify-items: center;    /* horizontal: center of cell */
  justify-items: stretch;   /* horizontal: fill cell (default) */

  align-items: start;       /* vertical: top of cell */
  align-items: end;         /* vertical: bottom of cell */
  align-items: center;      /* vertical: center of cell */
  align-items: stretch;     /* vertical: fill cell (default) */

  /* Shorthand: align-items / justify-items */
  place-items: center center;

  /* Align the grid itself within the container */
  justify-content: center;  /* horizontal alignment of grid */
  align-content: center;    /* vertical alignment of grid */

  /* Shorthand: align-content / justify-content */
  place-content: center center;
}

Item Alignment (Single Item)

.item {
  justify-self: center;   /* horizontal position within cell */
  align-self: end;         /* vertical position within cell */

  /* Shorthand: align-self / justify-self */
  place-self: end center;
}

Alignment Cheat Sheet

                  Horizontal (inline)    Vertical (block)
All items:        justify-items          align-items
Single item:      justify-self           align-self
Grid itself:      justify-content        align-content

Shorthand:        place-items = align-items / justify-items
                  place-self  = align-self / justify-self
                  place-content = align-content / justify-content

Subgrid

Subgrid lets a child grid inherit track sizing from its parent grid. This solves the longstanding problem of aligning nested grid items with outer grid tracks.

.parent {
  display: grid;
  grid-template-columns: 200px 1fr 200px;
  gap: 1rem;
}

.child {
  grid-column: 1 / -1;         /* span all parent columns */
  display: grid;
  grid-template-columns: subgrid; /* inherit parent's column sizes */
}
Parent grid:
|  200px  |      1fr      |  200px  |

Child with subgrid:
|  200px  |      1fr      |  200px  |  <- same tracks as parent

Without subgrid:
|        child defines own columns          |

Subgrid Use Case: Card Grid with Aligned Content

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

.card {
  display: grid;
  grid-template-rows: subgrid;  /* inherit row sizing from parent */
  grid-row: span 3;             /* card spans 3 parent rows */
}

/* Now card title, body, and footer align across all cards */
+----------+  +----------+  +----------+
| Title    |  | Title    |  | Title    |  <- Row 1 (aligned)
+----------+  +----------+  +----------+
| Body     |  | Body     |  | Body     |  <- Row 2 (aligned)
| (long)   |  |          |  | (medium) |
+----------+  +----------+  +----------+
| Footer   |  | Footer   |  | Footer   |  <- Row 3 (aligned)
+----------+  +----------+  +----------+

Browser support: subgrid is supported in Chrome 117+, Firefox 71+, Safari 16+, Edge 117+. Check caniuse before using in production.

Real-World Grid Patterns

1. Classic Page Layout

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

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

2. Dashboard Grid

.dashboard {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: auto;
  gap: 1rem;
}

.widget-large {
  grid-column: span 2;
  grid-row: span 2;
}

.widget-wide {
  grid-column: span 2;
}

.widget-tall {
  grid-row: span 2;
}
+----------+----------+-----+-----+
|                     |     |     |
|   widget-large      |  W  |  W  |
|                     |     |     |
+----------+----------+-----+-----+
|    widget-wide      |           |
+----------+----------+ widget-   |
|    W     |    W     | tall      |
+----------+----------+-----------+

3. Responsive Card Grid (No Media Queries)

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}

This is one of the most powerful CSS Grid patterns. Cards are at least 280px, expand to fill space, and automatically reflow.

.gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(3, 200px);
  gap: 0.5rem;
}

.featured {
  grid-column: 1 / 3;
  grid-row: 1 / 3;
}

.gallery img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
+------------------+--------+--------+
|                  |  img   |  img   |
|    featured      +--------+--------+
|                  |  img   |  img   |
+--------+---------+--------+--------+
|  img   |  img    |  img   |  img   |
+--------+---------+--------+--------+

5. Magazine Layout

.magazine {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 1rem;
}

.headline {
  grid-column: 1 / 5;
  grid-row: 1 / 3;
}

.sidebar-story {
  grid-column: 5 / 7;
}

.story {
  grid-column: span 2;
}

.ad-banner {
  grid-column: 1 / -1;
}

6. Pricing Table

.pricing {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 1.5rem;
  align-items: start;
}

.plan {
  display: grid;
  grid-template-rows: auto 1fr auto;
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
}

.plan-header  { padding: 1.5rem; background: #f5f5f5; }
.plan-body    { padding: 1.5rem; }
.plan-footer  { padding: 1.5rem; }

7. Content Grid with Breakout Sections

.content-grid {
  display: grid;
  grid-template-columns:
    [full-start] 1fr
    [content-start] minmax(0, 65ch)
    [content-end] 1fr
    [full-end];
}

.content-grid > * {
  grid-column: content;
}

.content-grid > .full-width {
  grid-column: full;
}

This creates a centered content column with the ability for certain elements to break out to full width โ€” common in blog layouts.

8. Responsive Sidebar

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

@media (min-width: 768px) {
  .app {
    grid-template-columns: minmax(200px, 250px) 1fr;
  }
}

9. Form Layout

.form-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.75rem 1rem;
  align-items: center;
}

.form-grid label {
  text-align: right;
}

.form-grid .full-width {
  grid-column: 1 / -1;
}
    Name: [________________]
   Email: [________________]
 Message: [________________]
          [________________]
          [    Submit     ]   <- full-width

10. Overlapping Elements

Grid items can overlap by placing them in the same cells:

.hero {
  display: grid;
  grid-template: 1fr / 1fr;
}

.hero > * {
  grid-area: 1 / 1;  /* all items in same cell */
}

.hero-image {
  z-index: 1;
}

.hero-overlay {
  z-index: 2;
  background: rgba(0, 0, 0, 0.5);
}

.hero-text {
  z-index: 3;
  place-self: center;
  color: white;
}

Grid vs Flexbox Decision Tree

Start here:
  |
  v
Is the layout 1D (single row or column)?
  |
  Yes --> Use Flexbox
  |
  No --> Is the layout 2D (rows AND columns)?
           |
           Yes --> Use Grid
           |
           No --> Do items need to align on both axes?
                    |
                    Yes --> Use Grid
                    |
                    No --> Does content dictate sizing?
                             |
                             Yes --> Flexbox (content-driven)
                             |
                             No --> Grid (layout-driven)

Side-by-side comparison:

ScenarioFlexboxGrid
Navigation barBest choiceWorks but overkill
Card row (single)Best choiceWorks
Card grid (multiple rows)Works with wrapBest choice
Page layout (header/sidebar/main)PossibleBest choice
Centering one elementBest choiceWorks
Form layoutPossibleBest choice
Dashboard widgetsDifficultBest choice
Toolbar / button groupBest choiceWorks

Responsive Grid Patterns

Mobile-First Grid

/* Mobile: single column */
.grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}

/* Tablet: two columns */
@media (min-width: 768px) {
  .grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* Desktop: four columns */
@media (min-width: 1200px) {
  .grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

Fluid Grid (No Media Queries)

.fluid-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(250px, 100%), 1fr));
  gap: 1rem;
}

The min(250px, 100%) prevents overflow on very narrow screens where 250px exceeds the container width.

Container-Query-Aware Grid

.card-container {
  container-type: inline-size;
}

@container (min-width: 600px) {
  .card-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@container (min-width: 900px) {
  .card-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

Debugging CSS Grid

Browser DevTools

All major browsers have grid inspectors:

  1. Chrome/Edge: Inspect element, check the "Grid" overlay in Layout tab. Shows line numbers, area names, track sizes.
  2. Firefox: Best grid inspector. Color-coded overlays, shows all grid lines, named areas, and gaps.
  3. Safari: Inspect element, Layout tab shows grid overlay.

Common Grid Issues

Problem: Items overflowing the grid
  -> Check if you're using fr units (they divide available space)
  -> Use minmax(0, 1fr) to allow columns to shrink below content size
  -> Check for fixed-width content inside grid items

Problem: Grid not taking full height
  -> Set min-height: 100vh on the grid container
  -> Or height: 100% with parent also having height set

Problem: Gap not showing
  -> Verify display: grid is set
  -> Check if items span multiple tracks (gap only between tracks)

Problem: Items not aligning in named areas
  -> Check spelling of area names
  -> Verify areas form valid rectangles
  -> Each area name must be a single connected rectangle

Problem: auto-fit not working as expected
  -> Needs minmax() โ€” not just a fixed size
  -> repeat(auto-fit, 200px) doesn't stretch items
  -> repeat(auto-fit, minmax(200px, 1fr)) does

Advanced Techniques

Dense Packing for Galleries

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  grid-auto-rows: 150px;
  grid-auto-flow: dense;
  gap: 4px;
}

.gallery .wide  { grid-column: span 2; }
.gallery .tall  { grid-row: span 2; }
.gallery .large { grid-column: span 2; grid-row: span 2; }

Grid with CSS Variables

.grid {
  --columns: 3;
  --gap: 1rem;

  display: grid;
  grid-template-columns: repeat(var(--columns), 1fr);
  gap: var(--gap);
}

@media (max-width: 768px) {
  .grid { --columns: 1; }
}

@media (min-width: 769px) and (max-width: 1024px) {
  .grid { --columns: 2; }
}

Nesting Grids

.outer-grid {
  display: grid;
  grid-template-columns: 1fr 3fr;
  gap: 2rem;
}

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

Full-Bleed Layout

.wrapper {
  display: grid;
  grid-template-columns:
    1fr
    min(65ch, 100%)
    1fr;
}

.wrapper > * {
  grid-column: 2;
}

.wrapper > .full-bleed {
  grid-column: 1 / -1;
}

Performance Notes

CSS Grid performs well in modern browsers. A few tips:

  1. Avoid animating grid track sizes โ€” changing grid-template-columns triggers layout recalculation. Animate transforms or opacity instead.
  2. Use content-visibility: auto on off-screen grid items for large grids โ€” lets the browser skip rendering until items scroll into view.
  3. auto-fit / auto-fill with many items is efficient โ€” the browser optimizes track computation.
  4. Explicit grids are faster than implicit โ€” define grid-template-rows when you know the row count instead of relying on grid-auto-rows.
  5. Subgrid adds minimal overhead โ€” it inherits tracks rather than computing new ones.

Browser Support

FeatureChromeFirefoxSafariEdge
Basic Grid57+52+10.1+16+
gap66+61+12+16+
subgrid117+71+16+117+
Masonry (experimental)Flag onlyFlag onlyNoFlag only

Grid is safe for production. The only newer feature requiring caution is subgrid โ€” check your target browser requirements.

Quick Reference

PropertyApplies ToCommon Values
displaycontainergrid, inline-grid
grid-template-columnscontainertrack sizes, repeat(), minmax()
grid-template-rowscontainertrack sizes, repeat(), minmax()
grid-template-areascontainernamed area strings
gapcontainerlength values
grid-auto-rowscontainertrack sizes
grid-auto-columnscontainertrack sizes
grid-auto-flowcontainerrow, column, dense
justify-itemscontainerstart, end, center, stretch
align-itemscontainerstart, end, center, stretch
justify-contentcontainerstart, end, center, space-between, etc.
align-contentcontainerstart, end, center, space-between, etc.
grid-columnitemstart / end, span N
grid-rowitemstart / end, span N
grid-areaitemname, or row-start / col-start / row-end / col-end
justify-selfitemstart, end, center, stretch
align-selfitemstart, end, center, stretch

Key Takeaways

  • CSS Grid is the right tool for two-dimensional layouts.
  • grid-template-columns and grid-template-rows define the structure.
  • fr units divide remaining space proportionally.
  • repeat(auto-fit, minmax(250px, 1fr)) is the magic formula for responsive grids without media queries.
  • Named template areas make complex layouts readable and easy to restructure.
  • subgrid inherits parent grid tracks โ€” essential for aligning nested content.
  • auto-fit collapses empty tracks (items stretch). auto-fill keeps empty tracks (items don't stretch).
  • Grid and flexbox are complementary. Use grid for the overall page structure, flexbox for component internals.
  • Use negative line numbers (1 / -1) to span the entire grid.
  • Browser support is excellent โ€” safe for all modern browsers.

Found this helpful?

Support devsofus โ€” help us keep creating free dev guides.

Related Articles