Ricardo Castañeda Ricardo Castañeda - 4 months ago 803
CSS Question

Chrome / Safari not filling 100% height of a flex parent

I want to have a vertical menu with a specific height.

Each child must fill the height of the parent and have middle-aligned text.

The number of children is random, so I have to work with dynamic values.

Div

.container
contains a random number of children (
.item
) that always have to fill the height of the parent. To achieve that I used flexbox.

For making links with text aligned to the middle I am using
display: table-cell
technique. But using table displays requires using a height 100%.

My problem is that
.item-inner { height: 100% }
is not working in webkit (Chrome).


  1. Is there a fix for this problem?

  2. Or is there a different technique to make all
    .item
    fill the height of the parent with text vertical aligned to middle?



Example here jsFiddle, should be viewed in Firefox and Chrome



.container {
height: 20em;
display: flex;
flex-direction: column;
border: 5px solid black
}
.item {
flex: 1;
border-bottom: 1px solid white;
}
.item-inner {
height: 100%;
width: 100%;
display: table;
}
a {
background: orange;
display: table-cell;
vertical-align: middle;
}

<div class="container">
<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>

<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>

<div class="item">
<div class="item-inner">
<a>Button</a>
</div>
</div>
</div>




Answer

Okay, let's go over the problem first then the solution.

The Problem

My problem is that .item-inner { height: 100% } is not working in Webkit (Chrome).

It's not working because you're using percentage heights in a way that doesn't conform with the traditional implementation of the spec.

CSS height property

percentage
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 and this element is not absolutely positioned, the value computes to auto.

auto
The height depends on the values of other properties.

In other words, for percentage height to work on an in-flow child, the parent must have a set height.

In your code, the top-level container has a defined height: .container { height: 20em; }

The third-level container has a defined height: .item-inner { height: 100%; }

But between them, the second-level container – .itemdoes not have a defined height. Webkit sees that as a missing link.

.item-inner is telling Chrome: give me height: 100%. Chrome looks at the parent (.item) and says: 100% of what? I don't see anything (ignoring the flex: 1 rule that is there). As a result, it applies height: auto (content height), in accordance with the spec.

Firefox, on the other hand, has begun accepting a parent's flex height as a reference for the child percentage height. IE11/Edge accepts flex heights, as well.

Here are some examples of Firefox and IE using a parent's flex height as reference for a child's percentage height:


Two Solutions

1. CSS Relative & Absolute Positioning

Apply position: relative to the parent and position: absolute to the child.

Size the absolutely positioned child with height: 100% and width: 100%, or use the offset properties: top: 0, right: 0, bottom: 0, left: 0.

With absolute positioning, percentage height works without a specified height on the parent.

2. Nested Flex Containers (Recommended)

Stick with flexbox all the way through.

Apply display: flex to the flex item (.item), making it a flex container. This automatically sets align-items: stretch, which tells the child (.item-inner) to expand the full height of the parent.

This method allows you to:

  • Get rid of percentage heights.
  • Get rid of table properties.
  • Get rid of vertical-align.

Try this (no changes to HTML):

.container {
    display: flex;
    flex-direction: column;
    height: 20em;
    border: 5px solid black
}

.item {
    display: flex;                      /* new; nested flex container */
    flex: 1;
    border-bottom: 1px solid white;
}

.item-inner {
    display: flex;                      /* new; nested flex container */
    flex: 1;                            /* new */

    /* height: 100%;                    <-- remove; unnecessary */
    /* width: 100%;                     <-- remove; unnecessary */
    /* display: table;                  <-- remove; unnecessary */  
}

a {
    display: flex;                      /* new; nested flex container */
    flex: 1;                            /* new */
    align-items: center;                /* new; vertically center text */
    background: orange;

    /* display: table-cell;             <-- remove; unnecessary */
    /* vertical-align: middle;          <-- remove; unnecessary */
}
<div class="container">
  <div class="item">
    <div class="item-inner">
      <a>Button</a>
    </div>
  </div>

  <div class="item">
    <div class="item-inner">
      <a>Button</a>
    </div>
  </div>

  <div class="item">
    <div class="item-inner">
      <a>Button</a>
    </div>
  </div>
</div>

jsFiddle


More Information