Why is my Grid element's height not being calculated correctly?

I'm having trouble with a CSS grid element's height. The code I am using is:

.gridContainer {
  border: thin solid black;
  background: rgba(255, 0, 0, 0.5);
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  width: 100px;
  height: 100px;
  grid-template-areas: 'windowContentHolder';
}

.gridItem {
  grid-area: windowContentHolder;
  background: rgba(255, 255, 0, 0.5);
  width: 200%;
  height: 200%;
}

.content {
  background: rgba(255, 0, 0, 0.5);
}
<div class="gridContainer">
  <div class="gridItem">
    <div class="content">hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>
    </div>
  </div>
</div>

As you can see the gridItem is set to be height:200% and the expected result is not as intended. It should be twice as high (200px) as the parent (100px), with any extra height being hidden by the scroll bar, instead though the height property doesn't seem to be setting at all.

It's seems like the percentage is considering the child height instead of the parent height because if we inspect closely the element we will see that its height is twice the height of the child element.

enter image description here

The element with 'hi' is not overflowing as would be expected. Changing the gridContainer to 'block' does work as expected, but not with 'grid':

.gridContainer {
  border: thin solid black;
  background: rgba(255, 0, 0, 0.5);
  display: block;
  width: 100px;
  height: 100px;
}

.gridItem {
  grid-area: windowContentHolder;
  background: rgba(255, 255, 0, 0.5);
  width: 200%;
  height: 200%;
  overflow: auto;
}

.content {
  background: rgba(255, 0, 0, 0.5);
}
<div class="gridContainer">
  <div class="gridItem">
    <div class="content">hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>
    </div>
  </div>
</div>

The height of the grid container is fixed at 100px.

The height of the grid item is set to 200%.

A grid item exists inside tracks, which exist inside the container.

You can think of grid items as existing two levels down from the container.

Put another way, the parent of the grid item is the track, not the container.

Since your row height is neither fixed or a true unit of length – it's set to 1fr – the grid item's percentage height fails and it is free to expand the row as necessary (height: auto).

Whatever fixed height you set on the container, set it on the row, as well.

.gridContainer {
  border: thin solid black;
  background: rgba(255, 0, 0, 0.5);
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: 100px;    /* adjustment; value was 1fr */
  width: 100px;
  height: 100px;
  grid-template-areas: 'windowContentHolder';
}

.gridItem {
  grid-area: windowContentHolder;
  background: rgba(255, 255, 0, 0.5);
  width: 200%;
  height: 200%;
  overflow: auto;
}

.content {
  background: rgba(255, 0, 0, 0.5);
}
<div class="gridContainer">
  <div class="gridItem">
    <div class="content">hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>
    </div>
  </div>
</div>

new answer

Finally found a logic explanation to this behavior.

First we need to understand the behavior of 1fr which is equal to minmax(auto,1fr) and as explained in this question, it will default to the the minimum content that's why our grid area is bigger than the defined height:100px

If we replace it by minmax(0,1fr) will will have our grid area equal to the container height and then our item will still be twice bigger.

.gridContainer {
  border: thin solid black;
  background: rgba(255, 0, 0, 0.5);
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: minmax(0,1fr);
  width: 100px;
  height: 100px;
  grid-template-areas: 'windowContentHolder';
}

.gridItem {
  grid-area: windowContentHolder;
  background: rgba(255, 255, 0, 0.5);
  width: 200%;
  height: 200%;
}

.content {
  background: rgba(255, 0, 0, 0.5);
}
<div class="gridContainer">
  <div class="gridItem">
    <div class="content">hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>hi<br/>
    </div>
  </div>
</div>

For the second behavior (the height:200%) we need to refer to the specification where we can read:

Once the size of each grid area is thus established, the grid items are laid out into their respective containing blocks. The grid area’s width and height are considered definite for this purpose.

So the grid area is first defined either based on the content if we use 1fr or based on the container height if we use minmax(0,1fr). Then that height is used as a reference for our percentage height.

That's why in all the cases, the grid item will end being twice bigger than the grid area and in your case the content defined that height.


old answer

First, let's start by defining how percentage height works:

Specifies a percentage height. The percentage is calculated with respect to the height of the generated box's containing block. If the height of the containing block is not specified explicitly (i.e., it depends on content height), and this element is not absolutely positioned, the value computes to 'auto'.ref

There is also another part of the specification dealing with percentage values and cycles:

Sometimes the size of a percentage-sized box’s containing block depends on the intrinsic size contribution of the box itself, creating a cyclic dependency. When calculating the intrinsic size contribution of such a box , a cyclic percentage—that is, a percentage value that would resolve against a containing block size which itself depends on that percentage—is resolved specially ... (then a lot of rules)

In some particular cases, the browser is able to resolve percentage values even if the containing block height is not specified explicitly.


As Michael_B said the element exists inside tracks that are inside the container so our containing block here is no more the container but the track.

Grid track is a generic term for a grid column or grid row—in other words, it is the space between two adjacent grid lines. Each grid track is assigned a sizing function, which controls how wide or tall the column or row may grow, and thus how far apart its bounding grid lines are. Adjacent grid tracks can be separated by gutters but are otherwise packed tightly.ref

How are these elements sized?

The tracks (rows and columns) of the grid are declared and sized either explicitly through the explicit grid properties or are implicitly created when items are placed outside the explicit grid. The grid shorthand and its sub-properties define the parameters of the gridref

We can also read more about here: 6.2. Grid Item Sizing, here: 6.6. Automatic Minimum Size of Grid Items and also here 7.2. Explicit Track Sizing


All these specifications are somehow difficult to follow so here is my own simplified interpretation to understand what is happening.

The size of the tracks is first calculated by the browser considering content and grid properties and then this height is used as reference for the percentage value.

Here is another example to better show what is happening:

.box {
  display: grid;
  height: 100px;
  width: 200px;
  grid-template-columns: 1fr 1fr;
  grid-gap:5px;
  background: red;
}

.box>div {
  background: rgba(0, 0, 0, 0.4);
  height: 200%;
}
<div class="box">
  <div class="first">
    lorem<br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br>
  </div>
  <div class="second">
    lorem<br> lorem
    <br> lorem
    <br> lorem
    <br>
  </div>
</div>

We have a container with display:grid and 2 columns, the first column contains more content than the second one and we have applied to both height:200%. Surprisingly both have the height which is twice the content height of the first one!

If we check the dev tools we can see this:

enter image description here

The dotted boxes define our track size where inside we have two grid cells. Since it's a grid the height of the track will be defined by the tallest content which will also make both cells to have that same height. So height:200% is not exactly the height of the content but the height of the track that was initially defined by the content.

If we check again the answer of Micheal_B, explicitely defining the track height will also give us a logical and trivial result since the calculation is easy and we don't have a complex cycle.

.box {
  display: grid;
  height: 100px;/*this will not be considered*/
  width: 200px;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 150px;/* this will be considered*/
  grid-gap: 5px;
  background: red;
}

.box>div {
  background: rgba(0, 0, 0, 0.4);
  height: 200%;
}
<div class="box">
  <div class="first">
    lorem<br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br>
  </div>
  <div class="second">
    lorem<br> lorem
    <br> lorem
    <br> lorem
    <br>
  </div>
</div>

enter image description here

As we can see, I specified the track to be 150px thus height:200% is equal to 300px.


This is not the only case. I also found another case with flexbox where we can use percentage height without any explicit height on the containing block.

.container {
  height:200px;
  background:red;
  display:flex;
}
.box {
  width:100px;
  background:blue;
}

.box > div {
  background:yellow;
  height:50%;
}
<div class="container">
  <div class="box">
    <div></div>
  </div>
</div>

As we can see the height:50% is working fine making the yellow box to be 50% of its parent element (the blue box).

The explication is related to the default stretch behavior of flexbox. By default, a flex item doesn't have a height defined by content but its height is stretched to fill the parent container height so the browser will calculate a new height which make the percentage value of the child to be relatively to this height.

If the flex item has align-self: stretch, redo layout for its contents, treating this used size as its definite cross size so that percentage-sized children can be resolved. ref

Here is another example that shows a similar behavior of the grid example:

.box {
  display: flex;
  width: 200px;
  background: red;
}

.box>div {
  background: rgba(0, 0, 0, 0.4);
}
.second >div {
  height:200%;
  background:yellow;
  width:50px;
}
<div class="box">
  <div class="first">
    lorem<br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br>
  </div>
  <div class="second">
    <div></div>
  </div>
</div>

The height of the parent is defined by the tallest element, the second element is stretched to that height and the height of yellow element is twice that same height.

In other words, what we have is somehow logical because even if we didn't specify an explicit height, the browser is able to first calculate the height of the containing block then resolve the percentage using the calculated value.

UPDATE

Here is another intresting result consider the top property. We also know that percentage value of top also refers to the height of the containing block of the element and this height need to be defined.

Here is an illustration:

.box {
  border:5px solid;
}
.box > div {
  position:relative;
  top:100%; 
  height:100px;
  background:red;
}
<div class="box">
  <div>This div won't move because the parent has no height specified</div>
</div>

<div class="box" style="height:100px;">
  <div>This div will move because the parent has a height specified</div>
</div>

Now if we consider our previous cases, top will work with percentage values like height is doing.

With CSS-grid

.box {
  display: grid;
  height: 100px;/*this will not be considered*/
  width: 200px;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 150px;/* this will be considered*/
  grid-gap: 5px;
  background: red;
}

.box>div {
  position:relative;
  top:100%;
  background: rgba(0, 0, 0, 0.4);
  height: 200%;
}
<div class="box">
  <div class="first">
    lorem<br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br>
  </div>
  <div class="second">
    lorem<br> lorem
    <br> lorem
    <br> lorem
    <br>
  </div>
</div>
<div class="b">
  <div class="a">
  
  </div>
</div>

With flexbox:

.box {
  display: flex;
  width: 200px;
  background: red;
}

.box>div {
  background: rgba(0, 0, 0, 0.4);
}
.second >div {
  position:relative;
  top:100%;
  height:200%;
  background:yellow;
  width:50px;
}
<div class="box">
  <div class="first">
    lorem<br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br> lorem
    <br>
  </div>
  <div class="second">
    <div></div>
  </div>
</div>