Learning CSS Grid

+-------------------------+ +---+
|                         | |   |
|  CSS Grid               | |   |
|  Layout Module          | |   |
|                         | |   |
+-------------------------+ |   |
+----+ +------------------+ |   |
|    | |                  | |   |
|    | |                  | |   |
|    | +------------------+ +---+
|    | +------+ +---------------+
|    | |      | |               |
+----+ +------+ +---------------+

CSS Grid is here! As of March 2017, all major browsers support it. Given this good news I decided to spend some time to better understand how to use it. The learning curve was much steeper than I expected – the module introduces something like 17 new concepts. It was quite overwhelming to take it all in, so I started breaking things down, taking notes and making demos, which I am going to share here. This is by no means an exhaustive guide. It might not even be 100% factually correct. But this should be enough information for you to form a basic mental model of how CSS Grids work and start building layouts with it.

Terminology

Before we start creating grid layouts we need to learn a few new terms. A Grid Container is an element on which display: grid has been applied. All its direct descendants become Grid Items.

When we define the grid we specify the number of rows and columns we want. The grid layout system then generates numbered lines which divide the grid horizontally and vertically. These are known as Grid Lines. The space between any two adjacent grid lines is called Grid Track. This is essentially a generic term for columns and rows.

Grid Lines


+---+---+---+---+---+---+ 1
|   |   |   |   |   |   |
|   |   |   |   |   |   |
|   |   |   |   |   |   |
+-------+---+-----------+ 2
|   |xxx|   |   |   |   |
|   |xxx|<-- Grid Cell  |
|   |xxx|   |   |   |   |
+-----------------------+ 3
|   |   |   |   |   |   |
|   |   |   |   |   |   |
|   |   |   |   |   |   |
+---+---+---+---+---+---+ 4
1   2   3   4   5   6   7
Row Tracks


+---+---+---+---+---+---+
|xxxxxxxxxxxxxxxxxxxxxxx|
|xxxxxxxxxxxxxxxxxxxxxxx|
|xxxxxxxxxxxxxxxxxxxxxxx|
+-----------------------+
|   |   |   |   |   |   |
|   |   |   |   |   |   |
|   |   |   |   |   |   |
+-----------------------+
|   |   |   |   |   |   |
|   |   |   |   |   |   |
|   |   |   |   |   |   |
+---+---+---+---+---+---+
Column Tracks


+---+---+---+---+---+---+
|xxx|   |   |   |   |   |
|xxx|   |   |   |   |   |
|xxx|   |   |   |   |   |
+xxx--------------------+
|xxx|   |   |   |   |   |
|xxx|   |   |   |   |   |
|xxx|   |   |   |   |   |
+xxx--------------------+
|xxx|   |   |   |   |   |
|xxx|   |   |   |   |   |
|xxx|   |   |   |   |   |
+---+---+---+---+---+---+
The grid gives us numbered lines to use when positioning items. A grid track is the space between any two lines on the grid.

A Grid Cell is the smallest unit on the grid. It is the intersection of a row and column. Conceptually it is quite similar to a table cell.

Grid Area is the space created by the intersection of four grid lines. In other words, it is a collection of one or more adjacent grid cells. The grid area can only be rectangular! It is not possible to create T- or L-shaped grid areas. Which for some reason was the first thing I wanted to do.

Grid Area


+---+---+---+---+---+---+
|   |   |   |   |   |   |
|   |   |   |   |   |   |
|   |   |   |   |   |   |
+---+---+-----------+---+
|   |   |xxxxxxxxxxx|   |
|   |   |xxxxxxxxxxx|   |
|   |   |xxxxxxxxxxx|   |
+---+---|xxxxxxxxxxx|---+
|   |   |xxxxxxxxxxx|   |
|   |   |xxxxxxxxxxx|   |
|   |   |xxxxxxxxxxx|   |
+---+---+-----------+---+
Grid items can span one or more cells – by row or by column – this is called a grid area.

Defining a Grid

There are many different ways to define a grid. I’m going to start by focusing on a basic scenario: 4 columns x 5 rows. grid-template-rows & grid-template-columns allow us to define the count and sizing of rows and columns respectively. We can list out the tracks as shown in the example below.

.my-grid-container {
  display: grid;
  grid-template-columns: 20px 20px 20px 20px;
  grid-template-rows: 20px 20px 20px 20px 20px;
}
.my-grid-container
+---+----+----+----+-------------+
|   |    |    |    |             |
|   |    |    |    |             |
+---+----+----+----+             |
|   |    |    |    |             |
|   |    |    |    |             |
+---+----+----+----+             |
|   |    |    |    |             |
|   |    |    |    |             |
+---+----+----+----+             |
|   |    |    |    |             |
|   |    |    |    |             |
+---+----+----+----+             |
|   |    |    |    |             |
|   |    |    |    |             |
+---+----+----+----+             |
|                                |
|                                |
|                                |
|                                |
+--------------------------------+
A basic grid with 4 columns and 5 rows. The rows are defined using grid-template-rows and columns using grid-template-columns.

We have a grid ✅ Not the most useful grid ever, but a grid nonetheless. In the above example, each row is 20px tall and each column is 20px wide. The one thing to note here is that grid-template-rows & grid-template-columns allow us to define the grid tracks. The browser then generates the grid lines automatically. These lines are invisible. They are there to help us with positing items on the grid, but do not impact our design visually. That said, being able to see these lines is extremely helpful for debugging. Luckily for us Firefox has a built-in Grid Inspector which visualizes the grid for us 🙌🏽

In this example, the grid has fixed track sizes. Which is a fancy way of saying that rows and/or columns have fixed widths. Therefore, the grid will remain the same size regardless of how big/small the container is.

Flexible Grids

We can also define grids with flexible track sizes. Or even have both fixed and flexible tracks at the same time! (See the example below.) Here percentage values refer to a percentage of the grid container.

.my-weird-but-flexible-grid {
  display: grid;
  grid-template-columns: 20px 5% 80px 40%;
  grid-template-rows: 4rem 2vh 12% 80px 20em;
}
.my-weird-but-flexible-grid

+-+--+----+----------------------+
| |  |    |                 |    |
+-+--+----+-----------------+    |
+-+--+----+-----------------+    |
| |  |    |                 |    |
| |  |    |                 |    |
+-+--+----+-----------------+    |
| |  |    |                 |    |
+-+--+----+-----------------+    |
| |  |    |                 |    |
| |  |    |                 |    |
| |  |    |                 |    |
| |  |    |                 |    |
| |  |    |                 |    |
+-+--+----+-----------------+    |
|                                |
|                                |
|                                |
|                                |
|                                |
+--------------------------------+
A flexible grid where we are using a mixture of units for track measurements such as, pixels, percentage, viewport units and ems.

The fr Unit

Let’s look at another example of a flexible grid. In this case we are still building a 4x5 grid, but we want the rows and columns to stretch out and fill the entire container. To do this, we will use the fr unit.

At this point you are probably asking yourself. The what unit? 🤷🏽‍♂️

.my-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr 1fr 1fr;
}
  /*
    The above can also be written as:
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: repeat(5, 1fr);
  */
.my-grid

+------+------+------+------+
|      |      |      |      |
|      |      |      |      |
|      |      |      |      |
+------+------+------+------+
|      |      |      |      |
|      |      |      |      |
|      |      |      |      |
+------+------+------+------+
|      |      |      |      |
|      |      |      |      |
|      |      |      |      |
+------+------+------+------+
|      |      |      |      |
|      |      |      |      |
|      |      |      |      |
+------+------+------+------+
|      |      |      |      |
|      |      |      |      |
|      |      |      |      |
+------+------+------+------+
A flexible grid with 4 columns and 5 rows. It is made flexible by setting the rows and columns to 1fr.

We have a new unit! The fr unit represents a fraction of the available space in the grid container. Have you ever used flex: 1 & flex: 2, etc? This works exactly like that. You can specify track sizing as a ratio of free space, eg: 1fr 2fr.

📝 We can define track sizing as length, percentage or fr.

Gutters

It wouldn’t be a real grid without some gutters. For this we can use grid-column-gap and grid-row-gap properties, or the shorthand grid-gap. The grid only generates gutters between the tracks. There is no gutter before the first track or after the last track.

⚠️ Even though there is a gap between two adjacent tracks there is still only one grid line.

.my-grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-template-rows: 1fr 1fr 1fr 1fr 1fr;
  grid-gap: 1rem;
}
.my-grid


1     2      3      4     5
+----+-+----+-+----+-+----+ 1
|    | |    | |    | |    |
|    | |    | |    | |    |
+----+ +----+ +----+ +----+
+----+ +----+ +----+ +----+ 2
|    | |    | |    | |    |
|    | |    | |    | |    |
+----+ +----+ +----+ +----+
+----+ +----+ +----+ +----+ 3
|    | |    | |    | |    |
|    | |    | |    | |    |
+----+ +----+ +----+ +----+
+----+ +----+ +----+ +----+ 4
|    | |    | |    | |    |
|    | |    | |    | |    |
+----+ +----+ +----+ +----+
+----+ +----+ +----+ +----+ 5
|    | |    | |    | |    |
|    | |    | |    | |    |
+----+-+----+-+----+-+----+ 6
A flexible 4x5 grid with gutters. Gutters can be created by using grid-gap property.

Positioning Grid Items

Now that we have defined a grid, let’s explore some options for how to place items on it. Much like flexbox, grid placement is not dependent on source order. This allows us to create complex layouts that can be drastically re-arranged using media queries. More on that later. Let’s focus on some basics for now.

Positioning items on a grid is quite similar to using absolute positioning. Except, instead of specifying locations in measurement units we specify start and end grid lines.

                      .my-grid

                      1     2      3      4     5
                      +----+-+----+-+----+-+----+ 1
                      |    | |    | |    | |    |
                      |    | |    | |    | |    |
                      |    | |    | |    | |    |
   grid-row-start (2)-------------> o-----------o <-----(5) grid-column-end
grid-column-start (3) |    | |    | |xxxxxxxxxxx|
                      |    | |    | |xxxxxxxxxxx|
                      |    | |    | |xxxxxxxxxxx|
                      +----+ +----+ |xxxxxxxxxxx|
                      |    | |    | |xxxxxxxxxxx|
                      |    | |    | |xxxxxxxxxxx|
                      |    | |    | |xxxxxxxxxxx|
grid-row-end (4)------------------> o-----------o 4
                      |    | |    | |    | |    |
                      |    | |    | |    | |    |
                      |    | |    | |    | |    |
                      +----+ +----+ +----+ +----+ 5
                      |    | |    | |    | |    |
                      |    | |    | |    | |    |
                      |    | |    | |    | |    |
                      +----+-+----+-+----+-+----+ 6
We can position items on the grid using grid-column-start, grid-column-end and grid-row-start, grid-row-end.

Grid lines are automatically numbered. Starting with 1 for the topmost and leftmost lines. We use these numbers to declare grid-column-start / grid-column-end and grid-row-start / grid-row-end. We can also start counting from the end. For that we start with -1 for the rightmost and topmost lines. Lastly, we can also specify these locations as spans. For example, grid-row-end: span 3 means that the end location is starting grid line + 3 tracks.

Below is a CodePen that demonstrates a few different positioning styles. Notice how grid items can overlap. We can use the z-index property to control the stacking order.

🤖 Automate All the Things

I want to end by showcasing a couple more concepts through an example. I am going to replicate this product grid from the Aldo website. A few things to note here:

  • It is a 4x5 grid on large devices (> 60em)
  • There are 13 items placed on it
  • Some items span 2 columns and/or 2 rows
  • All images have a ratio of 1000 : 1270
  • There is a 1rem gutter and a 1rem padding all around the grid
  • The maximum width of the grid is restricted to 60em

The grid also changes based on break points. It reduces to 3 columns on medium-sized devices (30em ↔️ 60em) and 2 columns on small devices < 30em. Finally, we need to maintain 1000 : 1270 ratio between the row and column track sizing.

Large-Breakpoint
  min-width: 60em
(4 columns)


+-----------+ +-----------+
|  wide 2   | |           |
|           | |           |
+-----------+ |   wide 2  |
+----+ +----+ |   tall 2  |
|    | |    | |           |
|    | |    | |           |
+----+ +----+ +-----------+
+----+ +----+ +----+ +----+
|    | |    | |    | |    |
|    | |    | |    | |    |
+----+ +----+ +----+ +----+
+-----------+ +----+ +----+
|           | |    | |    |
|           | |    | |    |
|  wide 2   | +----+ +----+
|  tall 2   | +----+ +----+
|           | |    | |    |
|           | |    | |    |
+-----------+ +----+ +----+

|<----max-width: 60em---->|
Medium-Breakpoint
  min-width: 30em
  and max-width: 60em
(3 columns)

+------------------+
|      wide 3      |
|                  |
+------------------+
+-----------+ +----+
|           | |    |
|           | |    |
|  wide 2   | +----+
|  tall 2   | +----+
|           | |    |
|           | |    |
+-----------+ +----+
+----+ +----+ +----+
|    | |    | |    |
|    | |    | |    |
+----+ +----+ +----+
+-----------+ +----+
|           | |    |
|           | |    |
|  wide 2   | +----+
|  tall 2   | +----+
|           | |    |
|           | |    |
+-----------+ +----+
+----+ +----+ +----+
|    | |    | |    |
|    | |    | |    |
+----+ +----+ +----+
Small-Breakpoint
  min-width: 60em
(2 columns)


+-----------+
|  wide 2   |
|           |
+-----------+
+-----------+
|           |
|           |
|  wide 2   |
|  tall 2   |
|           |
|           |
+-----------+
+----+ +----+
|    | |    |
|    | |    |
+----+ +----+
+----+ +----+
|    | |    |
|    | |    |
+----+ +----+
+----+ +----+
|    | |    |
|    | |    |
+----+ +----+
+-----------+
|           |
|           |
|  wide 2   |
|  tall 2   |
|           |
|           |
+-----------+
+----+ +----+
|    | |    |
|    | |    |
+----+ +----+
+----+ +----+
|    | |    |
|    | |    |
+----+ +----+
The aldo style product grid. 2 columns on small devices, 3 on medium and 4 on large devices.

Defining the Row and Column Sizes

Previously, we used something like grid-template-columns: repeat(4, 1fr) and grid-template-rows: repeat(5, 1fr) to generate the grid. Because of the 1000 : 1270 ratio we can no longer do that. Instead, we need to use a bit of math to figure out the row and column sizes. For this we can use CSS variables and calc 🏄 We can calculate the width of the columns and height of the rows using this formula:

width = (width_of_grid - (gutters + padding)) / number_of_columns;
height = (1270 * width) / 1000;
Break PointWidth of GridGutters + PaddingNumber of Columns
Default (less than 30em)100vw(1 + 2)rem2
min-width: 30em and
max-width: 60em
100vw(2 + 2)rem3
min-width: 60em60em(3 + 2)rem4

So, for the default case width = (100vw - 3rem) / 2. Awesome! Let’s put it all together. We start by defining the default value in :root. Then at each break point we recalculate width and update the grid-template-columns property.

:root {
  --width: calc((100vw - 3rem) / 2);
  --height: calc(1270 * var(--width) / 1000);
}

.layout {
  grid-gap: 1rem;
  grid-template-columns: repeat(2, var(--width));
  grid-auto-rows: var(--height);
}

@media screen and (min-width: 30em) and (max-width: 60em) {
  :root {
    --width: calc((100vw - 4rem) / 3);
  }
  .layout {
    grid-template-columns: repeat(3, var(--width));
  }
}

@media screen and (min-width: 60em) {
  :root {
    --width: calc((60em - 5rem) / 4);
  }
  .layout {
    grid-template-columns: repeat(4, var(--width));
  }
}

Auto Rows

In the code snippet above you might have noticed grid-auto-rows: var(--height). This tells the browser to automatically generate just enough rows to fit all the items. So if we add more items, it will add more rows and vice versa. The value for grid-auto-rows is what we want the height of the row to be.

The CodePen below demonstrates what the grid would look like at this point. Open it in the edit mode to see how the resizing works and what happens when you add or remove items.

📝 Did you notice that we did not have to place the items on the grid?! If you don’t specify positioning then the browser automatically places items on the grid. This is known as auto flow.

Item Spans ↔️

As I mentioned earlier, some items span multiple tracks. For this we can use the grid-<row|column>-end: span <number> technique. The span size for the images remains the same across all break points. However, the product info spans 3 columns on medium-sized devices and 2 columns for all other breakpoints. Therefore, we apply wide-2 wide-3-m to it. The other items use wide-2 tall-2.

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

@media screen and (min-width: 30em) and (max-width: 60em) {
  .wide-3-m {
    grid-column-end: span 3;
  }
}

Want to learn more about auto placement? Check out Rachel Andrew’s fantastic tutorial and demo.

Dense Packing

We have a grid. We used auto placement to position items and some items span multiple columns & rows. So we’re done? Not quite. If you inspect the above CodePen in edit mode you will notice that the grid ends up with holes at certain break points.

holes in the grid

This is because by default auto placement uses a sparse packing algorithm which can lead to holes in the layout. The good news is that we can switch to a dense packing algorithm which causes the browser to backtrack and fill any empty cells in the layout party parrot

.layout {
  grid-gap: 1rem;
+ grid-auto-flow: dense;
  grid-template-columns: repeat(2, var(--width));
  grid-auto-rows: var(--height);
}

That’s it! Below is the final version. And here’s another example that uses auto layout and dense packing to display music albums.

DemoSource

Conclusion

Take a moment to think about the possibilities 🤔 Think of all the crazy layouts we can build now. Can’t think of any? Not a problem. Hop on over to Jen Simmons’s Layout Lab for some ideas. CSS Grids will likely have a drastic impact on web design. All those layouts you abandoned because they were not possible with CSS will likely be possible thanks to CSS Grids.

What Next? Did you know you can name grid lines? Did you know you can align items in a track using align and justify properties? Did you know you can specify sizes as a range using minmax? There is so much more to discover! I know it can feel a bit overwhelming. Take little steps. Build things. Experiment. It’s okay to not understand everything at once.

Further Reading

Questions, Comments or Suggestions? Open an Issue

Creative coding from a front-end developer's perspective

My goal with this blog is to go beyond the basics. Breakdown my sketches. And make animation math approachable. The newsletter is more of that.

Hear about what's got my attention—new tools and techniques I've picked up. Experiments I'm working on. And previews of upcoming posts.