HorusKol

Using grid areas to control layout

August 31, 2022

A stylised grid of white lines on a blackground with three regions filled in with green, yellow and blue.

A while ago, I wrote about using named grid lines to control layout, but I didn't realise that I had not also written about my favourite way of laying out grids - grid-template-areas. Time to fix that.

Grid areas are pretty darn awesome - instead of specifying how many columns and rows you want a child item to occupy in a grid, or setting the start and end lines, you just say "put this here" with the grid-area property.

.card {
  display: grid;
  grid-template-columns: 6rem 1fr;
  grid-template-rows: 2rem 6rem;
  grid-template-areas: "name    name"
                       "picture description";
}

.card .card-name {
  grid-area: name; 
}

.card .card-picture {
  grid-area: picture;
}

.card .card-description {
  grid-area: description;
}
<div class="card">
    <div class="card-name"></div>
    <div class="card-picture"></div>
    <div class="card-description"></div>
</div>

You can arrange areas within a grid anyway you like, with a couple of caveats:

  1. All areas must be rectangular;
  2. Each row must have the same number of columns.

However, you do not have to name every cell. The . character can be used to indicate an empty space within your grid. This is a rather extreme example:

.card {
  display: grid;
  grid-template-columns: 6rem 1fr;
  grid-template-rows: 2rem 6rem;
  grid-template-columns: 6rem repeat(3, 1fr);
  grid-template-rows: 2rem repeat(3, 6rem);
  grid-template-areas: "name    name name        name"
                       "picture .    .           ."
                       "picture .    description ."
                       "picture .    .           .";
}

Free named lines

One neat thing about using grid areas is that you get some free named lines thrown in. Each named area gets a *-start and *-end line you can reference:

.card {
  display: grid;
  grid-template-columns: 6rem 1fr;
  grid-template-rows: 2rem 6rem;
  grid-template-columns: 6rem repeat(2, 1fr);
  grid-template-rows: 2rem repeat(2, 6rem);
  grid-template-areas: "name    name        name"
                       "picture description description"
                       "picture description description";
}

.card .card-name {
  grid-area: name;
}

.card .card-picture {
  grid-area: picture;
}

.card .card-description {
  grid-row: description-start / description-end;
}
<div class="example-03">
    <div class="card-name"></div>
    <div class="card-picture"></div>
    <div class="card-description"></div>
    <div class="card-description"></div>
</div>

Grid areas and document order

Say we want to surround a hero image with other smaller ones:

HERO
1
2
3
4
5
6
7
8
9
10
11
12
.gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, max-content);
  grid-template-areas: ". .    .    ."
                       ". hero hero ."
                       ". hero hero ."
                       ". .    .    .";
}

.gallery .hero {
  grid-area: hero;
}

It might be tempting to write the HTML in "grid order" (please assume that there are good alt descriptions set for each of these images):

<div class="gallery">
    <!-- Top row -->
    <img src="first.jpg"/>
    <img src="second.jpg"/>
    <img src="third.jpg"/>
    <img src="fourth.jpg"/>

    <!-- Second row -->
    <img src="fifth.jpg"/>
    <img src="hero.jpg" class="hero"/>
    <img src="sixth.jpg"/>

    <!-- Third row -->
    <img src="seventh.jpg"/>
    <img src="eighth.jpg"/>

    <!-- Bottom row -->
    <img src="ninth.jpg"/>
    <img src="tenth.jpg"/>
    <img src="eleventh.jpg"/>
    <img src="twelfth.jpg"/>
</div>

There's a few reasons not to do it this way:

  1. Screen readers will simply follow document order - meaning that screen reader users will not know about your hero until after they've gone through several other images already.
  2. Similarly, if you wanted to display this on a narrow screen, the easiest thing to do is make the gallery container a simple block, but the hero image will be buried below several other images.
  3. Finally, if you want to change the layout in grid-template-areas in the future, the grid order changes.

If something is visually distinct within a document, it is probably a good idea to place it near the top of the document (as a general rule, and there will always be exceptions):

<div class="gallery">
    <img src="hero.jpg" class="hero"/>

    <!-- Top row -->
    <img src="first.jpg"/>
    <img src="second.jpg"/>
    <img src="third.jpg"/>
    <img src="fourth.jpg"/>
    <img src="fifth.jpg"/>
    <img src="sixth.jpg"/>
    <img src="seventh.jpg"/>
    <img src="eighth.jpg"/>
    <img src="ninth.jpg"/>
    <img src="tenth.jpg"/>
    <img src="eleventh.jpg"/>
    <img src="twelfth.jpg"/>
</div>

Responsive design

As mentioned above, the simplest thing to do with a grid as a screen/window gets smaller is to collapse it by changing the container's display property to block. This will immediately invalidate all the other grid properties, and the contents will render in the document order.

But it's also possibly to adapt the layout in grid-template-areas instead, so we can do things like this:

HERO
1
2
3
4
5
6
7
8
9
10
11
12
.gallery {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, min-content);
  grid-template-areas: ". .    .    ."
                       ". hero hero ."
                       ". hero hero ."
                       ". .    .    .";
}

@media (max-width: 768px) {
  .gallery {
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: repeat(auto-fit, min-content);
    grid-template-areas: "hero hero";
  }
}

.gallery .hero {
  grid-area: hero;
}

Or you could try something like this:

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

In both cases, the only thing we have to change to be responsive is the parent's grid properties - the child elements will automatically move around as the window resizes.

A plugin for Tailwind CSS

While I'm happy to work with vanilla CSS, I also use Tailwind CSS a lot in various projects. Because of this, I created a plugin that helps create utilities for grid areas - Grid Areas for Tailwind CSS.

// tailwind.config.js
{
    //...
    theme: {
      extend: {
        gridTemplateAreas: {
          'layout': [
            'header header header',
            'nav    main   main',
            'nav    footer footer',
          ],
        },
        gridTemplateColumns: {
          'layout': '24rem 1fr 2rem',
        },
        gridTemplateRows: {
          'layout': '6rem
          3rem
          1fr
          auto',
        },
    }
    //...
}
<body class="grid grid-areas-layout grid-cols-layout grid-rows-layout h-full">
    <header class="grid-in-header"></header>
    <nav class="grid-in-nav"></nav>
    <main class="grid-in-main"></main>
    <footer class="grid-in-footer"></footer>
</body>