Search code examples
htmlcssflexboxpercentage

Why does having an extra wrapper with display flex change height percentage behaviour?


I'd like to understand why the green div with cyan border behaves like so in these cases.

Without extra wrapper:

.wrapper {
  display: flex;
  border: solid 2px black;
}

.red {
  width: 25px;
  height: 25px;
  background-color: red;
}

.green {
  height: 100%;
  background-color: green;
  border: solid 2px cyan;
}

.blue {
  width: 50px;
  height: 50px;
  background-color: blue;
}
<div class="wrapper">
  <div class="red"></div>
  <div class="green"></div>
  <div class="blue"></div>
</div>

Result: Green div has zero height instead of blue's height.

With extra wrapper. Both with display flex:

.wrapper {
  display: flex;
  border: solid 2px black;
}

.red {
  width: 25px;
  height: 25px;
  background-color: red;
}

.green {
  height: 100%;
  background-color: green;
  border: solid 2px cyan;
}

.blue {
  width: 50px;
  height: 50px;
  background-color: blue;
}
<div class="wrapper">
  <div class="wrapper">
    <div class="red"></div>
    <div class="green"></div>
    <div class="blue"></div>
  </div>
</div>

Result: The green div has blue's height, as expected.

With extra wrapper. Only the grandparent with display flex:

.wrapper {
  display: flex;
  border: solid 2px black;
}

.red {
  width: 25px;
  height: 25px;
  background-color: red;
}

.green {
  height: 100%;
  background-color: green;
  border: solid 2px cyan;
}

.blue {
  width: 50px;
  height: 50px;
  background-color: blue;
}
<div class="wrapper">
  <div>
    <div class="red"></div>
    <div class="green"></div>
    <div class="blue"></div>
  </div>
</div>

Result: The green div has blue + red + (cyan border * 2) height, as expected.

Why do I have to add a wrapper with display flex to make the height percentage work the way I expect it to work?


EDIT

Doing some tinkering I discovered something. In the first example, removing height: 100% (or setting it to auto) makes it grow. Why is it that when the green div has height auto it takes all the height of the parent (minus its own border) but not when it is set as 100%?

.wrapper {
  display: flex;
  border: solid 2px black;
}

.red {
  width: 25px;
  height: 25px;
  background-color: red;
}

.green {
  height: auto;
  background-color: green;
  border: solid 2px cyan;
}

.blue {
  width: 50px;
  height: 50px;
  background-color: blue;
}
<div class="wrapper">
  <div class="red"></div>
  <div class="green"></div>
  <div class="blue"></div>
</div>


Solution

  • First example (Without extra wrapper)

    The green div with height: '100%' is set to be the same height with its containing block, which is the parent div with display: 'flex'. However, the height of the parent div is indefinite, so that height: '100%' will behave like height: 'auto'. And there is no content in the green div, so that it will has zero height.

    W3C CSS standards: height - percentage

    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'.

    W3C CSS standards: containing block

    W3C CSS standards: definite & indefinite size

    Second example (With extra wrapper. Both with display flex)

    The green div's containing block is still its parent div with display: 'flex', also the extra wrapper. Here is a difference, the wrapper has a parent with display: 'flex', which means the wrapper is a flex item now. According to flex layout algorithm, size of the wrapper will be considered definite. Now 100% will be effective, make the green div has the same height with the wrapper.

    Discard some details, the layout procedure is like this:

    1. Determine grandparent's height, depends on its children.
    2. Determine the wrapper's height, depends on its children.
    3. Collect all items and find the largest height.
      1. Red has height 25px.
      2. Green has height: '100%', but its containing block's height is indefinite now, so that treat as height: 'auto', which gives content height 0 + border * 2. Green has height 4px.
      3. Blue has height 50px.
      4. The largest is 50px.
    4. The wrapper's height is determined to be 50px.
    5. The grandparent's height is determined to be 50px. Then the heights of its children are set to be 50px and definite.
    6. Redo layout of contents of the wrapper.
      1. Red and Blue are still set to be 25px and 50px.
      2. Green's height is now calculated to be 100% of its definite parent's height, 50px. The total height will be added border * 2, 54px.
    7. Redo layout of contents of the Red, Green and Blue, nothing to do.

    W3C CSS standards: flex layout algorithm - Definite Sizes

    Once the cross size of a flex line has been determined, items in auto-sized flex containers are also considered definite for the purpose of layout; see step 11.

    W3C CSS standards: flex layout algorithm - Cross Size Determination

    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.

    Third example (With extra wrapper. Only the grandparent with display flex)

    The procedure is similar to the second example. The differences are bold and italic.

    1. Determine grandparent's height, depends on its children.
    2. Determine the wrapper's height, depends on its children.
    3. Calculate total height of content.
      1. Red has height 25px.
      2. Green has height: '100%', but its containing block's height is indefinite now, so that treat as height: 'auto', which gives content height 0 + border * 2. Green has height 4px.
      3. Blue has height 50px.
      4. The total is 25px + 4px + 50px = 79px.
    4. The wrapper's height is determined to be 79px.
    5. The grandparent's height is determined to be 79px. Then the heights of its children are set to be 79px and definite.
    6. Redo layout of contents of the wrapper.
      1. Red and Blue are still set to be 25px and 50px.
      2. Green's height is now calculated to be 100% of its definite parent's height, 79px. The total height will be added border * 2, 83px.
    7. Redo layout of contents of the Red, Green and Blue, nothing to do.

    BTW

    1. I prefer to set box-sizing: 'border-box' to avoid bother of adding border line width.
    2. If you want to align the three items, remove height from the green of the first example. Flex layout has align-items: stretch by default, which will stretch the green's height to align the highest one in its cross line. Otherwise explicit height will override the stretch.