The difference between percentage and fr units

Solution 1:

fr

The fr unit works only with the free space in the container.

So in your code:

grid-template-columns: repeat(12, 1fr);

... the free space in the container is distributed equally among 12 columns.

Since the columns are only dealing with free space, grid-column-gap is not a factor. It was subtracted from the container width before the fr length was determined (spec reference).

Here's how the browser does the calculation:

(free space - gutters) / 12  = 1fr

%

When you're using percentages...

grid-template-columns: repeat(12, calc(100% / 12));

... the container is divided into 12 columns, each having a width of 8.33333%. This is an actual length, unlike the fr unit, which only deals with free space.

Both the column lengths and grid gaps are factored into the width.

Here's how the browser does the calculation:

8.33333% * 12 = 100%
         +
11 * 10px     = 110px

There's a clear overflow.

(Note: grid-*-gap properties apply only between grid items – never between items and the container. That's why the number of grid-gaps is 11, not 13.)

This works:

grid-template-columns: repeat(12, calc(8.3333% - 9.1667px));

Which breaks down to this:

  • 12 columns

  • the width of each column is determined by taking the full width of the container (100%) and dividing it by 12

    100% / 12 = 8.3333% (individual column width)
    
  • then subtract the column gaps (there are 11)

     10px * 11 = 110px (total width of column gaps)
    
    110px / 12 = 9.1667px (amount to be deducted from each column)
    

.grid {
  display: grid;
  grid-template-columns: repeat(12, calc(8.3333% - 9.1667px));
  grid-column-gap: 10px;
  grid-row-gap: 10px;
  justify-content: center;
}

.l-1 { grid-column-start: span 1; }
.l-2 { grid-column-start: span 2; }
.l-3 { grid-column-start: span 3; }
.l-4 { grid-column-start: span 4; }
.l-5 { grid-column-start: span 5; }
.l-6 { grid-column-start: span 6; }
.l-7 { grid-column-start: span 7; }
.l-8 { grid-column-start: span 8; }
.l-9 { grid-column-start: span 9; }
.l-10 { grid-column-start: span 10; }
.l-11 { grid-column-start: span 11; }
.l-12 { grid-column-start: span 12; }
[class*=l-] { border: 1px solid red; }
<div class="grid">
  <div class="l-6">Column 1</div>
  <div class="l-6">Column 2</div>
  <div class="l-3">Column 3</div>
  <div class="l-4">Column 4</div>
  <div class="l-3">Column 5</div>
  <div class="l-2">Column 6</div>
  <div class="l-1">Column 7</div>
  <div class="l-10">Column 8</div>
  <div class="l-1">Column 9</div>
  <div class="l-5">Column 10</div>
  <div class="l-5">Column 11</div>
  <div class="l-2">Column 12</div>
</div>

Solution 2:

According to this part of the spec the fr unit is not a length so it gets "calculated" AFTER determining the amount of free space available within the layout engine.

The variable you have created in your first example IS part of a calculation (100% of the width and dividing by 12) so it runs the calculation BEFORE passing to the layout engine.

When I say layout engine, I'm using it as a metaphor and don't want to confuse folks with the process of rendering that gets done by the browser. I'm just trying to say that in your first example you are presenting a series of numbers that get plugged into the browser to begin the rendering process and in your second example you are presenting more of an algorithm/function the browser can use to make its layout.

Solution 3:

Late reply but 100% worth it.

I have to add to Michael Benjamin one important thing, specified in the 3.

Summary with all the cases collected.

1. Usage with %

Grid columns calculated with % are not taking into accounts gutters (aka gaps). Therefore you need to add the pixels of the added gaps to the calculation. so totalGridWidth = SUM(...%) + gutters = ~100% + gutters

2. Usage with fr

The previous issue does not happen (exception on number 3.) as it includes to calculate the free space as well with the gaps. so calculation is as follow: (free space - gutters) / 12 = 1fr therefore here you could get ratios as fractions instead of portions as percentages.

Or in other words:

grid-template-columns: repeat(12, 1fr);

3. Usage with minmax(0,Xfr)

By default the browser layout engine uses to calculate Xfr this formula minmax(auto,Xfr) which relies on the minimum size of your items, and when any of those items or inner elements are expected to grow in size indefinitely with things like width:100% the auto parameter will make still case 2. to run sometimes with overflown grids. To prevent this, we need to force the browser to use a method that can shrink the elements until its real minimum, which is 0px to do this you need to use minmax(0,Xfr) with X as the desired fraction.

Or in other words, for your previous case:

grid-template-columns: repeat(12, minmax(0,1fr));

This will be a bulletproof for your overflowing issues.

You can read more in this article I have found:

https://css-tricks.com/preventing-a-grid-blowout/