Chrome / Safari not filling 100% height of flex parent
CssGoogle ChromeWebkitFlexboxCss Problem Overview
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).
- Is there a fix for this problem?
- 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>
Css Solutions
Solution 1 - Css
Solution
Use nested flex containers.
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.
Important: Remove specified heights from flex items for this method to work. If a child has a height specified (e.g. height: 100%
), then it will ignore the align-items: stretch
coming from the parent. For the stretch
default to work, the child's height must compute to auto
(full explanation).
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>
Explanation
> 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 – .item
– does 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 numerical value works (auto
won't), 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>
Four 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.
Important: Remove specified heights from flex items for this method to work. If a child has a height specified (e.g. height: 100%
), then it will ignore the align-items: stretch
coming from the parent. For the stretch
default to work, the child's height must compute to auto
(full explanation).
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 2 - Css
Specifying a flex attribute to the container worked for me:
.container {
flex: 0 0 auto;
}
This ensures the height is set and doesn't grow either.
Solution 3 - Css
Solution: Remove height: 100%
in .item-inner and add display: flex
in .item
Solution 4 - Css
For Mobile Safari There is a Browser fix. you need to add -webkit-box for iOS devices.
Ex.
display: flex;
display: -webkit-box;
flex-direction: column;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-webkit-flex-direction: column;
align-items: stretch;
if you're using align-items: stretch;
property for parent element, remove the height : 100%
from the child element.
Solution 5 - Css
I have had a similar issue in iOS 8, 9 and 10 and the info above couldn't fix it, however I did discover a solution after a day of working on this. Granted it won't work for everyone but in my case my items were stacked in a column and had 0 height when it should have been content height. Switching the css to be row and wrap fixed the issue. This only works if you have a single item and they are stacked but since it took me a day to find this out I thought I should share my fix!
.wrapper {
flex-direction: column; // <-- Remove this line
flex-direction: row; // <-- replace it with
flex-wrap: wrap; // <-- Add wrapping
}
.item {
width: 100%;
}