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>

1 upvote
  flag
1 upvote
  flag
This isn't specifically relevant to OP, but might be relevant for googlers who land here for "safari display absolute height 100% doesnt work" or something similar. I have an element like this inside a flex container and had to specify top:0 and left:0 to have it appear as expected. – AlexMA
1 upvote
  flag
@vsync hasn't been fixed yet: //allinonescript.com/questions/46226298/… – TylerH

2 Answers 11

up vote 235 down vote accepted

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 height in a way that doesn't conform with the traditional implementation of the spec.

10.5 Content height: the 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 to the parent (.item) for reference and responds: 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, now accepts a parent's flex height as a reference for the child's percentage height. IE11 and Edge accept flex heights, as well.

Also, Chrome will accept flex-grow as an adequate parent reference if used in conjunction with flex-basis (any value works, including flex-basis: 0). As of this writing, however, this solution fails in Safari.

#outer {
  display: flex;
  flex-direction: column;
  height: 300px;
  background-color: white;
  border: 1px solid red;
}
#middle {
  flex-grow: 1;
  flex-basis: 1px;
  background-color: yellow;
}
#inner {
  height: 100%;
  background-color: lightgreen;
}
<div id="outer">
  <div id="middle">
    <div id="inner">
      INNER
    </div>
  </div>
</div>


Solutions

1. Specify a height on all parent elements

A reliable cross-browser solution is to specify a height on all parent elements. This prevents missing links, which Webkit-based browsers consider a violation of the spec.

Note that min-height and max-height are not acceptable. It must be the height property.

More details here: Working with the CSS height property and percentage values

2. CSS Relative & Absolute Positioning

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

Size the 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.

3. Remove unnecessary HTML containers (recommended)

Is there a need for two containers around button? Why not remove .item or .item-inner, or both? Although button elements sometimes fail as flex containers, they can be flex items. Consider making button a child of .container or .item, and removing gratuitous mark-up.

Here's an example:

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

a {
    flex: 1;
    background: orange;
    border-bottom: 1px solid white;
    display: flex;                   /* nested flex container (for aligning text) */
    align-items: center;             /* center text vertically */
    justify-content: center;         /* center text horizontally */
}
<div class="container">
    <a>Button</a>
    <a>Button</a>
    <a>Button</a>
</div>

4. Nested Flex Containers (recommended)

Get rid of percentage heights. Get rid of table properties. Get rid of vertical-align. Avoid absolute positioning. Just 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.

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

Solution: Remove height: 100% in .item-inner and add display: flex in .item

Demo: https://codepen.io/tronghiep92/pen/NvzVoo

upvote
  flag
Thanks, this helped me. Do flex children automatically take stretch to the height of their container? Is this why this works? – Reece Kenney
upvote
  flag
@ReeceKenney, yes, to your questions. This is explained in my answer. See solution #4. – Michael_B

Not the answer you're looking for? Browse other questions tagged or ask your own question.