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:

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>

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>