CSSIntermediate

CSS Grid Areas and Responsive Grids: auto-fit and minmax

Draw a page layout with grid-template-areas, rearrange it by changing one rule, and build card grids that adapt with auto-fit, auto-fill and minmax().

All CSS lessons

What you will learn

In the Grid basics lesson you built grids by counting columns and rows. That works, but a page layout like “header on top, navigation on the left, content in the middle, footer at the bottom” is easier to understand when you can simply draw it. Grid lets you do exactly that with grid-template-areas. You will also go deeper into the responsive-card-grid pattern: what auto-fit, auto-fill and minmax() really do, how to make one card span the whole row, and how to stop grids from overflowing on tiny screens.

The HTML we will style

<h1 class="page-title">Grid Areas Lab</h1>

<!-- Part 1: a page layout drawn with named areas -->
<div class="layout">
  <header class="header">Header</header>
  <nav class="nav">Nav</nav>
  <main class="main">Main content</main>
  <aside class="aside">Aside</aside>
  <footer class="footer">Footer</footer>
</div>

<!-- Part 2: a responsive card grid -->
<section class="cards">
  <article class="card card-featured">
    <h3 class="card-title">Featured lesson</h3>
    <p class="card-text">This card spans the whole row.</p>
  </article>
  <article class="card">
    <h3 class="card-title">HTML</h3>
    <p class="card-text">Structure.</p>
  </article>
  <!-- four more cards: CSS, JavaScript, Git, React -->
</section>

<!-- Part 3: auto-fit vs auto-fill with only three items -->
<h2 class="demo-heading">auto-fit</h2>
<div class="fit-demo">
  <div class="tile">1</div>
  <div class="tile">2</div>
  <div class="tile">3</div>
</div>

<h2 class="demo-heading">auto-fill</h2>
<div class="fill-demo">
  <div class="tile">1</div>
  <div class="tile">2</div>
  <div class="tile">3</div>
</div>
Selector Element Role
.layout the <div> that wraps five regions grid container for Part 1
.header, .nav, .main, .aside, .footer the five children of .layout grid items, each placed in a named area
.cards the <section> grid container for Part 2
.card the six <article>s grid items with a shared look
.card-featured the first card the item that spans the whole row
.card-title, .card-text heading and text inside each card text styling
.fit-demo, .fill-demo two containers in Part 3 grid containers that differ by one keyword
.tile the six numbered boxes grid items

Part 1: drawing a layout with grid-template-areas

Instead of counting lines, you give each region a name, then draw the layout with those names in a string:

/* the <div class="layout"> */
.layout {
  display: grid;
  grid-template-columns: 160px 1fr 140px;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header header"
    "nav    main   aside"
    "footer footer footer";
  gap: 8px;
  min-height: 280px;
}

Each line of text in grid-template-areas is one row, and each word is one column cell. The picture is almost the page itself:

            160px         1fr          140px
          +-----------+-------------+-----------+
   auto   |             header (spans 3)         |
          +-----------+-------------+-----------+
   1fr    |    nav    |    main     |   aside   |
          +-----------+-------------+-----------+
   auto   |             footer (spans 3)         |
          +-----------+-------------+-----------+

Then each child says which named area it belongs to, with grid-area:

/* each region is placed by NAME, not by line numbers */
.header { grid-area: header; }   /* the <header class="header"> */
.nav    { grid-area: nav; }      /* the <nav class="nav"> */
.main   { grid-area: main; }     /* the <main class="main"> */
.aside  { grid-area: aside; }    /* the <aside class="aside"> */
.footer { grid-area: footer; }   /* the <footer class="footer"> */

Here is how the three pieces connect:

Where What you write Meaning
On the container grid-template-areas: "header header header" ... draws the layout, row by row
On each item grid-area: header; “I am the region called header”
On the container grid-template-columns and grid-template-rows the actual sizes of the tracks

The rules of the drawing

  • Every row must have the same number of names.
  • An area that appears several times must form a solid rectangle. "header header header" is fine. An L shape is not allowed.
  • Use a dot (.) for an empty cell, for example "nav main .".
  • The names are plain words, not quotes: header, not "header". (The quotes surround each row.)
  • The names are separate from class names. They only match the grid-area values. It is convenient to use the same words as the classes, but it is not required.
  • You do not have to use a name for every item. An item without a grid-area is placed automatically in the next free cell, like any other.

Rearranging the layout with one rule

The big benefit: to rearrange the layout, you edit only the drawing. The HTML and the item rules stay exactly the same. For example, a single-column layout for narrow screens:

@media (max-width: 600px) {
  .layout {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "nav"
      "main"
      "aside"
      "footer";
  }
}

A media query applies its rules only when a condition is true, here “the screen is 600px wide or narrower”. You will study them properly in the next lesson. For now, notice how little changes: five lines of “drawing”. Resize the preview to see the layout stack.

You can also do things that would be awkward with floats or Flexbox, like a sidebar that runs the full height of the page:

grid-template-areas:
  "nav header"
  "nav main"
  "nav footer";

A quick tip: look at the grid in DevTools

In Chrome and Edge, the Elements panel shows a small grid badge next to any grid container. Click it to draw the grid lines and area names on the page. Firefox has an even richer grid inspector. When a layout is not doing what you expect, turn the overlay on first.

Part 2: the responsive card grid, in depth

You met this pattern last lesson. Now let us take it apart.

/* the <section class="cards"> */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}

/* each <article class="card"> */
.card {
  padding: 20px;
  border: 1px solid #dddddd;
  border-radius: 12px;
  background-color: white;
}

.card-title {
  margin: 0 0 8px;
}

.card-text {
  margin: 0;
  color: #475569;
}

Read repeat(auto-fit, minmax(220px, 1fr)) in three steps:

  1. minmax(220px, 1fr): every column is at least 220px wide, and at most one equal share of the free space.
  2. repeat(auto-fit, ...): put as many of those columns on a row as will fit in the container.
  3. Whatever space is left over is shared among the columns, so each card stretches to fill the row neatly.

So a 1000px container fits four 220px columns (880px plus gaps), and each stretches to about 240px. At 700px you get three, at 500px two, and at 300px just one. The browser works this out; you wrote no breakpoints.

Making one card span the whole row

/* the <article class="card card-featured"> */
.card-featured {
  grid-column: 1 / -1;
  background-color: #eef2ff;
}

Grid lines can be numbered from the end with negative numbers: -1 is the last line. So 1 / -1 means “from the first line to the last line”, which is the full width, however many columns there currently are. It is much more flexible than writing span 3.

auto-fit vs auto-fill

These two keywords behave the same when there are plenty of items. The difference shows when there are fewer items than the columns that would fit:

/* both <div class="tile"> groups have just three tiles */
.fit-demo {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
  gap: 8px;
}

.fill-demo {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
  gap: 8px;
}

.tile {
  padding: 20px;
  background-color: royalblue;
  color: white;
  text-align: center;
}
  • auto-fit collapses the empty columns to nothing, so the three tiles stretch to fill the whole row.
  • auto-fill keeps the empty columns, so the three tiles stay at their natural size and empty space remains on the right.

Use auto-fit when you want items to fill the row (most card grids). Use auto-fill when you want items to keep a consistent column size even if the last row is short (for example a photo gallery where a lonely image should not become huge).

Stopping overflow on tiny screens

There is a hidden problem. If the container itself is narrower than the minimum (say a 200px-wide phone view with minmax(220px, 1fr)), the column cannot shrink below 220px, so it overflows and causes sideways scrolling. The fix is to let the minimum itself be capped by the container width, using the min() function:

.cards {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
}

min(100%, 220px) means “220px, but never wider than the container”. It is a good habit to use this form whenever you write an auto-fit grid.

minmax(0, 1fr) for stubborn content

A plain 1fr really means minmax(auto, 1fr), and auto stops a column from shrinking below the size of its content. A long word, a wide table or a code block can therefore stretch the track and break the layout. Writing minmax(0, 1fr) lets the column shrink to zero, and the content must then wrap or scroll inside it:

.layout {
  grid-template-columns: 160px minmax(0, 1fr) 140px;
}

It is the Grid twin of the min-width: 0 trick you used with Flexbox.

Putting it together

*, *::before, *::after { box-sizing: border-box; }
body { font-family: Arial, Helvetica, sans-serif; }

/* Part 1: the named-area page layout */
.header, .nav, .main, .aside, .footer {
  padding: 16px;
  color: white;
  font-weight: bold;
}

.header { grid-area: header; background-color: #1e293b; }
.nav    { grid-area: nav;    background-color: tomato; }
.main   { grid-area: main;   background-color: royalblue; }
.aside  { grid-area: aside;  background-color: seagreen; }
.footer { grid-area: footer; background-color: #475569; }

.layout {
  display: grid;
  grid-template-columns: 160px minmax(0, 1fr) 140px;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header header"
    "nav    main   aside"
    "footer footer footer";
  gap: 8px;
  min-height: 280px;
  margin-bottom: 32px;
}

@media (max-width: 600px) {
  .layout {
    grid-template-columns: 1fr;
    grid-template-areas:
      "header"
      "nav"
      "main"
      "aside"
      "footer";
  }
}

/* Part 2: the responsive cards */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: 16px;
  margin-bottom: 32px;
}

.card {
  padding: 20px;
  border: 1px solid #dddddd;
  border-radius: 12px;
}

.card-featured {
  grid-column: 1 / -1;
  background-color: #eef2ff;
}

.card-title { margin: 0 0 8px; }
.card-text  { margin: 0; color: #475569; }

/* Part 3: auto-fit vs auto-fill */
.fit-demo,
.fill-demo {
  display: grid;
  gap: 8px;
}

.fit-demo  { grid-template-columns: repeat(auto-fit,  minmax(100px, 1fr)); }
.fill-demo { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); }

.tile {
  padding: 20px;
  background-color: royalblue;
  color: white;
  text-align: center;
}

Common mistakes

  • Rows with a different number of names in grid-template-areas. The whole property is then invalid and ignored.
  • Drawing an area that is not a rectangle (an L or a T shape).
  • Forgetting grid-area on the items, or spelling a name differently from the drawing. The item then ignores the layout.
  • Putting quotes around the names inside the strings or forgetting the quotes around each row.
  • Expecting grid-template-areas to size the tracks. It only names cells. The sizes still come from grid-template-columns and grid-template-rows.
  • Using auto-fit or auto-fill with a fixed track size like repeat(auto-fit, 1fr). They need a minimum size to know how many fit, so use minmax().
  • Letting minmax(220px, 1fr) overflow on small phones. Wrap the minimum in min(100%, 220px).
  • Confusing auto-fit and auto-fill: fit stretches the items, fill keeps empty columns.
  • Forgetting -1 for a full-width item and hard-coding span 3, which breaks when the number of columns changes.

Practice

  1. Click Practice in Editor. Change the layout drawing so the nav is on the right and the aside on the left. Do you need to touch the HTML?
  2. Draw a layout where the nav runs the full height of the page on the left, with the header, main and footer stacked on the right.
  3. Replace aside in the bottom row with a . (a dot). What happens to that cell?
  4. Add the media query for max-width: 600px and stack all five regions into one column. Resize the preview to test it.
  5. Build the card grid with repeat(auto-fit, minmax(220px, 1fr)) and resize. Where does it change from three columns to two?
  6. Make the first card span every column with grid-column: 1 / -1. Then try grid-column: span 2, and resize to see why -1 is more reliable.
  7. Compare auto-fit and auto-fill with the three tiles. Then add three more tiles to each. Do they still look different?
  8. Open the Elements panel in DevTools and click the grid badge next to .layout to display the area names.

Recap

  • grid-template-areas lets you draw a layout with named regions. Each string is a row, and each word is a cell.
  • Items choose their region with grid-area: name. To rearrange the layout, change only the drawing.
  • Areas must be rectangles, rows must have the same number of names, and a . means an empty cell. The track sizes still come from grid-template-columns and grid-template-rows.
  • repeat(auto-fit, minmax(220px, 1fr)) builds a responsive grid without breakpoints. auto-fit stretches the items, auto-fill keeps empty columns.
  • grid-column: 1 / -1 makes an item span the full row. Use minmax(min(100%, 220px), 1fr) to avoid overflow on tiny screens, and minmax(0, 1fr) for content that refuses to shrink.
  • Next you will learn the full toolbox for responsive design: mobile-first CSS, media queries and breakpoints.