CSS Grid: Understanding grid-gap and fr vs. auto units

CSS Grid is a game-changer to the way we can create modern web layouts. It is a leap forward in terms of power, simplicity, and creativity: it is easily the most important CSS tool I’ve learned in the past year. There is a lot to know in Grid, but Grid isn’t complicated: it just has many, many options for the ways you can write it in order to make your code as clear and flexible as possible. Here, I’m going to walk through some nuances of how Grid works in less flashy ways to understand more deeply how some of its new tools such as grid-gap and fr units work.

Some words to know: Grid can generate vertical columns and horizontal rows. These are bordered and separated by grid lines and both columns and rows can be called grid tracks. Tracks that we write into and define in our layout are called explicit tracks, while columns and rows that Grid will generate automatically based on our content are called implicit tracks. The intersection of a row and column creates a grid cell.

Starting with the display property

display: grid;

What does display: grid affect?

Like Flexbox, Grid begins with a parent container that affects child elements one level deep. An element given the display: grid declaration operates in the HTML flow as an ordinary block-level element, but internally, it is generating columns and rows in order to place these children. An element can only have one display value, so you can’t have an item be both display: flex and display: grid, or be a grid parent and also display: inline-block, for that matter.

Let’s say we want our <body> to be a grid parent:

/* CSS */
body {
  display: grid;
}
<!-- HTML -->
<body>
  <header></header> <!-- This is a grid child -->
  <main> <!-- So is this -->
   <section> 
     <!-- But this is two levels down, 
     so it's not a grid child -->
  </main>
  <footer></footer> <!-- Also a grid child -->
</body>

In the example above, we could use one set of code to define the grid on the header, main, and footer elements, and then add display: grid to make the main element its own grid parent of a new, separate internal grid that would affect the section element.

We can generate as many grids as we want and they don’t effect each other. In the future, we might be able to let element grandchildren see the grid above through “subgrids,” but CSS isn’t there yet.

What does display: grid do to an element?

An element with display: grid and no other grid code will still generate implicit rows and columns: a single column filling the width of the parent container, and rows for each block-level child element. This will display identically to HTML without Grid—unless the container has a set height. Then, the height of the implicit rows will expand to fill the space available.

Using grid-gap as a margin

Before Grid and Flexbox, separating HTML elements using box-model properties such as margin and padding was a lot of work. To properly separate items in a row, you might have to add padding-right to all of them and then set off a second line of code just to remove the padding from the final nth-child. To set bottom margins for text, you can’t use em units without having to account for different font-sizes for headings and paragraphs, which means either using rem units throughout or having to do a lot of math. Same goes for trying to calculate column widths with percent values while keeping track of margins. There is also no such thing as “margin collapse” with grid-gap.

grid-gap solves a lot of problems. It’s actually shorthand for two other properties:

grid-column-gap: [value];
grid-row-gap: [value];

And the shorthand can be given both row and column gap values, rows (think margin-bottom) first:

grid-gap: 10px 20px;

Or one value for both:

grid-gap: 10px;

grid-gap can take any unit value, such as px, em, %, vw, and only appears to separate grid children—that means does not have to be manually removed from the top, bottom, left, or right of child elements because it never appears there.

It draws its em value from the grid parent, which means em can be safely used with grid-row-gap as a replacement for margin-bottom for text content, allowing more flexibility to set different margins for individual page sections based on changing font sizes, instead of being stuck with rem units and needing to write in different numbers to keep up.

grid-gap is a key reason to use Grid over Flexbox for certain layouts—it is quietly one of the biggest headache-solvers in Grid.

So, some use cases:

  • Replace margin-bottom for single-column page/text content
  • Separate standard layout items like navigation, main, and footer without multiple padding or margin values
  • Photo galleries

Again, we don’t even need to declare any columns or rows to take advantage of this: we can use grid-gap for bottom margins just by using it with display: grid.

grid-gap issues

Because grid-gap abstracts away the calculations it takes to add up to 100% of the size of the grid parent, it doesn’t play nicely if you take up that space with % units.

For instance, a grid with grid-gap: 10px and two 50%-wide columns is not going to equal 100%: it will be 100% plus 10px, and will overflow the grid-parent.

It’s best not to use percentages in grid layouts. Instead, Grid introduces a new length unit, fr: a fractional unit. 1fr will occupy any available space in the row or column—it operates like % but in a way compatible with grid-gap without requiring any calc() fussiness. We can also use auto as a value, which is similar but different in an important way.

Auto and fr in Grid

Creating columns in Grid is as simple as writing out the width of each column we want in a grid-template, like this:

body {
  display: grid;
  grid-template-columns: 100px 100px 1fr;
  grid-gap: 10px;

This gives us two 100px-wide columns and one fractional column, which will take up 100% of the remaining space, after accounting for 20 pixels of grid-gap.

body {
  display: grid;
  grid-template-columns: 1fr 1fr;

Now we have two fractional columns, and a total fractional value of 2: thus, each one is going to split 1/2 of the remaining space.

body {
  display: grid;
  grid-template-columns: 2fr 1fr;

Now we have a fractional total of 3, so the first column has a value of 2/3, and the second has a value of 1/3, or 33.33%. Look, Ma, no calc!

In a declaration without fr units, auto will operate identically to 1fr.

body {
  display: grid;
  grid-template-columns: 100px auto;

A 100px column and a column filling the rest of the width.

body {
  display: grid;
  grid-template-columns: auto auto;

Two 50%-wide columns.

Both fr and auto have a minimum width of the length of their content: if space is available, they will take it, if no extra space is available, they will shrink to their content width.

Unlike percentages, auto and fr values can be expanded based on the size of their internal content.

body {
  display: grid;
  grid-template-columns: 1fr 1f 1fr;
  /* or */
  grid-template-columns: auto auto auto;

…is not the same as grid-template-columns: 33% 33% 33%; because a, let’s say, 1200px wide image will expand one of the columns past the expected 1/3 fraction. This can have unexpected results, depending on the size, even pushing out the horizontal width of your whole page for a large column item.

That’s because fractional and auto grid values check their child element sizes first, and then size accordingly, whereas a fixed-width image in a 33% column would collapse past the width boundary instead and run into the next grid column.

To keep things divided up as intended, we can account for this as we would with percent-based columns by setting a max-width: 100% on images or other objects that come with their own fixed width.

Auto and fr together

When fr and auto are used together, fr “wins” the fight for remaining space and auto loses its width value, shrinking down to the min-width of its element content.

body {
  display: grid;
  grid-template-columns: auto 1fr 2fr;

The first column auto-sizes to the natural width of its element and the remain space is divided into 1/3 and 2/3.

This combination is perfect for sidebars or elements of unknown size that don’t need a particular amount of column or row room. It also enables us to create sticky footers, a la Flexbox:

/* CSS */
body {
  height: 100vh;
  display: grid;
  grid-template-rows: auto 1fr auto;
<!-- HTML -->
<body> 
  <header> <!-- auto-sizes to natural height -->
  <main> <!-- 1fr takes up remaining 100vh of screen height -->
  <footer> <!-- auto-sizes to natural height -->
</body>

The advantage of doing it this way is we’re essentially setting flex-grow on the main section without having to add code in two places. We do still have to set a 100% or 100vh height for the screen for the rows to occupy.

Learn more Grid

This is just scratching the surface on what Grid can do. I would heartily recommend the following resources to keep going:

Layout Land: Basics of CSS Grid: The Big Picture
A terrific, clear introduction to Grid and where it stands in the CSS landscape from Mozilla’s Jen Simmons. I would recommend watching all of her videos on the Layout Land channel, especially her series on “resilient CSS” and how Grid and Flexbox can be used in tandem with older, more compatible code to create a good experience on all browsers.

Scrimba: Learn CSS Grid for Free
Scrimba is a new screencasting platform that lets you pause the video and interact with the code, live, in the video screen as if it were a text editor. It is honestly mind-blowing. This course is quick and very good.

Grid.io: Wes Bos’ free CSS Grid course
A thorough course that’s organized nicely and goes deep into what grid is capable of. Worth the time.