Learning CSS Grid
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.
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.
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.
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.
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? 🤷🏽♂️
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.
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.
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 a1rem
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.
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 Point | Width of Grid | Gutters + Padding | Number of Columns |
---|---|---|---|
Default (less than 30em) | 100vw | (1 + 2)rem | 2 |
min-width: 30em and max-width: 60em | 100vw | (2 + 2)rem | 3 |
min-width: 60em | 60em | (3 + 2)rem | 4 |
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.
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
.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.
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.