Getting to grips with CSS Grids

Grids are the latest layout model to join the CSS spec after flex. Like flex, grids open up a whole new world of layout possibilities that will make every web developer's life a whole lot easier. Unlike flex, grids allow you to use more than one dimension at a time. Flex is row OR column. Grid is row AND column.

As with flex there are some issues with support, but CSS Grids works on the latest major browsers so you can use it right now, but only if you don't need to support older browsers. NB: If a browser doesn't support it then it will just fall back to displaying items as block in HTML source order - so a basic stacked content layout.

For all of the demo code I'll be using a wrapper class of .grid-wrap. For most of the demos I'll also be using a class of .grid-item for the items inside the grid! So let's jump in to using grid…

.grid-wrap {
    display: grid;
}
<div class="grid-wrap">
    <div class="grid-item">1</div>
</div>

You are now using grid, but in the most basic of ways. You have a grid that is 1x1. 

The grid-item is automatically placed in the first available space on the grid that fits it's grid properties. Luckily for us that would be to be in the first row/column and be 1 row/column in size. Adding more items in the grid

Any element directly inside this grid will be fitted into the next available space that fit's it dimensions (eg. if the next space is 1 width but a cell is set to span 2 wide then it won't fit so will keep trying the next spaces until it fits, or will be added as a new row/column at the end of the grid if no spaces match it's requirements. 

The grid will expand to fit the new row/column or if the grid is constrained then the rows/columns will adapt to the required size or the grid will overflow (scroll).

.grid-wrap {
    display: grid;
}
<div class="grid-wrap">
    <div class="grid-item">1</div>
    <div class="grid-item">2</div>
    <div class="grid-item">3</div>
    <div class="grid-item">4</div>
</div>

Now that we have some more content for the grid, we should probably start to look at making a better grid than a 1x1! Defining a grid

A grid is made up of rows (horizontal) and columns (vertical). You can define your rows and columns in a number of ways, you can name them or just use their numerical order, and you can group rectangular portions of the grid into named areas. Let's start with the sizes we can use.

Row/Column size

All existing css unit sizes (px, %, em, rem, vh, vw) work with grids, plus there is a new size unit that can be used. The fractional unit  - fr. You use this just like any other unit (a number, then the unit: e.g 2fr, 5fr), the only difference being that a fractional unit is a fraction of the remaining space AFTER other units sizes have been accounted for. NOTE: Because a grid cell expands to fit it's content and since every fr is equal it means that, if your content expands one fr based cell then all fr based cells expand by the same ratio. Note that the fr vertically and horizontally are NOT linked so a change in height would not change width! Confused? It'll click soon enough...

You can also use the new intrinsic sizing properties of max-content min-content fit-content and available however support for these is currently patchy. Let's not even go there for a year or so!

For my examples I'm going to use the fr unit only, and mostly stick to it being 1fr across so we end up with a chessboard like shape just with less squares (mainly so the example code is smaller). I could use % to do the same initially but if I decided to add another column I would need to update all the widths and not just add a another!

Defining the rows and columns

Let's start by creating a 3x3 grid. I'm also going to give this grid a width and height so that the grid is easier to see…

.grid-wrap {
    display: grid;
    grid-template-rows: 1fr 1fr 1fr;
    grid-template-columns: 1fr 1fr 1fr;
    width: 900px;
    height: 900px;
}
<div class="grid-wrap">
    <div class="grid-item">1</div>
    <div class="grid-item">2</div>
    <div class="grid-item">3</div>
    <div class="grid-item">4</div>
</div>

Now the items will start to fill the cells. All 3 in the first row will fill up and the first one in the second row. Just like using floats or flex with wrap!.. But let's change that. First, lets give our items IDs so we can target each one individually. Then lets make a layout with a header, sidebar, content and footer…

.grid-wrap {
    display: grid;
    grid-template-rows: 1fr 1fr 1fr;
    grid-template-columns: 1fr 1fr 1fr;
    width: 900px;
    height: 900px;
}
#header {
    grid-row: 1;
    grid-column: 1 / span 2;
}
#side {
    grid-row: 2;
    grid-column: 1;
}
#content {
    grid-row: 2;
    grid-column: 2 / span 2;
}
#footer {
    grid-row: 3;
    grid-column: 1 / span 2;
}

`

<div class="grid-wrap">
    <div class="grid-item" id="header">1</div>
    <div class="grid-item" id="side">2</div>
    <div class="grid-item" id="content">3</div>
    <div class="grid-item" id="footer">4</div>
</div>

There are a few things to take in here. The number represents the line, not the cell. It starts at one edge of the grid, and ends at the other- it is not the number of the column like in excel! The 1st is the very edge of the grid, the 2nd is the line that ends the first cell and starts the next and so on. In our case that means we have 4 lines for each row/column. 

1 -
  fr
2 -
  fr
3 -
  fr
4 -
   | fr | fr | fr |
   1    2    3    4

With the #header we are saying that we would like the item to start on the 1st row line and we don't specify an end row line or alternatively how many rows it should span, so it uses the default of 1 row deep. We could have said:

grid-row: 1 / span 1;

or

grid-row: 1 / 2;

Because we want the #header to span all the way across we tell it to start at column 1 and then span 2 columns, just like we could have done with the row. Since we also want it to be all the way across we could have also said:

grid-column: 1 / -1;

The minus tells the grid to start counting from the other end and use the first column from the opposite side. Technically it's not from the right, it's from the opposite side because if you use a RTL language the the grid would be flipped so left becomes right! Confused yet?! We're going to go deeper soon!

You can also start/end a row/column in a reverse order just like you can travel east to west or west to east just as easily:

grid-column: 4 / 1;

is the same as:

grid-column: 1 / 4;

When you have a look at the #content and #footer you should be able to see what is going on and can change the CSS to use an alternate way of doing the same thing. As you get more familiar with the power of the grid then you'll understand the more robust ways to say tell it what you want to happen.

Naming row and column lines

All this number stuff is confusing so it would be handy if we could name these rows/columns, luckily those wise guys ate the W3C though the same! You don't have to name every row/column but if you wish to you just put a name in [square] brackets and bingo your row/column is named 'square'. 

.grid-wrap {
    display: grid;
    grid-template-rows: [top] 1fr [content] 1fr [footer] 1fr [bot];
    grid-template-columns: [start] 1fr [content] 1fr 1fr [end];
    width: 900px;
    height: 900px;
}

It's so good, you can also have multiple names (all in the same square brackets)!

grid-template-rows: [head] 1fr [head-end cont] 1fr [cont-end];

Now you can update your other CSS to use the names. I have replaced spans with the end row names just to labor the point a bit:

#header {
    grid-row: top / content;
    grid-column: start / end;
}
#side {
    grid-row: content / footer;
    grid-column: start / content;
}
#content {
    grid-row: content / footer;
    grid-column: content / content;
}
#footer {
    grid-row: footer / bot;
    grid-column: start / end;
}

Now this is all very well, but it's still a pain to remember where everything should start and end. It would be easier just to say

"hey you div, go in this area" - Me while developing a website

Well those guys at the W3C have thought of that too and that's called areas, but that's another post!