Reputation: 518
In the snippet below, the first row has two divs with flex-grow: 1
. As expected, each div takes up 50% of the screen.
When adding padding to the left div, that is no longer the case. Can someone explain why?
body > div {
height: 50px;
display: flex;
}
body > div > div {
flex: 1;
box-sizing: border-box;
}
#a {
background-color: red;
}
#b {
background-color: green;
}
#c {
padding: 10px;
background-color: blue;
}
#d {
background-color: yellow;
}
<div>
<div id="a"></div>
<div id="b"></div>
</div>
<div>
<div id="c"></div>
<div id="d"></div>
</div>
Upvotes: 38
Views: 22864
Reputation: 1469
Changing the box-sizing property to border-box in CSS incorporates padding and border into an element's total width and height, without affecting its size when you add padding or border.
A common practice is to apply box-sizing: border-box; globally to all elements in your CSS:
* {
box-sizing: border-box;
}
Upvotes: 0
Reputation: 273969
This is not a bug and the behavior is clearly described in the Specification (clear but not obvious).
Here it's about the flex base size. You can read:
Determine the flex base size and hypothetical main size of each item:
Note the hypothetical main size which is the important part here.
Each element is having flex:1
so flex-basis:0
which mean a flex base size equal to 0
A. If the item has a definite used flex basis, that’s the flex base size.
Now let's continue until the end and read the following:
When determining the flex base size, the item’s min and max main sizes are ignored
Until now, it's still ok. We ignore everything when finding the flex base size.
Let's continue:
The hypothetical main size is the item’s flex base size clamped according to its used min and max main sizes (and flooring the content box size at zero).
The hypothetical main size consider the element size so it will be different from the flex base size and in our case we have 20px
of padding so the hypothetical main size is equal to 20px
Now if you check the rest of the flexbox algorithm you will notice that the hypothetical main size is the one used which give us the logical result of the item with padding getting bigger.
For this step, the size of a flex item is its outer hypothetical main size
Sum the outer hypothetical main sizes of all items on the line
etc
It's more trivial if you remove flex-grow
body > div {
height: 50px;
display: flex;
}
body > div > div {
flex-basis: 0;
box-sizing: border-box;
}
#a {
background-color: red;
}
#b {
background-color: green;
}
#c {
padding: 10px;
background-color: blue;
}
#d {
background-color: yellow;
}
<div>
<div id="a"></div>
<div id="b"></div>
</div>
<div>
<div id="c"></div>
<div id="d"></div>
</div>
All of them have 0
width but the one with padding will have 20px
(its hypothetical main size not its flex base size)
The solution to avoid this is to make sure all the items end with the same hypothetical main size so we need to make sure the flex-basis
is bigger than all the defined padding, width, border, etc.
@Michael_B use flex-basis:50%
but you can also use flex-basis:40%
or flex-basis:20px
or any value bigger than 20px
and smaller than 50%
body > div {
height: 50px;
display: flex;
}
body > div > div {
flex-basis: 40%; /* between 20px and 50% will do the job*/
flex-grow:1;
flex-shrink:1;
box-sizing: border-box;
}
#a {
background-color: red;
}
#b {
background-color: green;
}
#c {
padding: 10px;
background-color: blue;
}
#d {
background-color: yellow;
}
<div>
<div id="a"></div>
<div id="b"></div>
</div>
<div>
<div id="c"></div>
<div id="d"></div>
</div>
Upvotes: 4
Reputation: 7587
Tldr: It is a bug. Reference1, Reference2
Alternative solution to Michael Benjamin's last example:
Apply flex-basis: 20px /*2 X padding of sibling*/
to the yellow div#d
body > div {
height: 50px;
display: flex;
}
body > div > div {
flex: 1;
box-sizing: border-box;
}
#a {
background-color: red;
}
#b {
background-color: green;
}
#c {
padding: 10px;
background-color: blue;
}
#d {
background-color: yellow;
flex-basis: 20px;
}
<div>
<div id="a"></div>
<div id="b"></div>
</div>
<div>
<div id="c"></div>
<div id="d"></div>
</div>
Why does the above solution work? The answer lies in the actual question to your answer.
Actual Question: Why does a flex-item, having flex-basis:0
not get width k1/(k1+k2) times flex-container
's width, when it has padding?
Answer: Because, the-effective-flex-basis of a flex-item is padding-left
+ padding-right + border_left_width + border_right_width
of that flex-item. The complex Mathematics that Michael is referring to is a sort pseudo-code for the whole algorithm used by user agents in determining flex-item
's length. In your specific case the algorithm boils down to:
if(flex_basis < (pading_left + padding_right + border_left_width + border_right_width)) {
computed_flex_basis = pading_left + padding_right + border_left_width + border_right_width;
}
For our case, we don't have any border, so let us ignore that for the moment. So we should have flex_basis = pading_left + padding_right
Proof:
body > div {
height: 50px;
display: flex;
}
body > div > div {
flex: 1; /*flex-basis: 0%*/
box-sizing: border-box; /*doesn't matter*/
}
.a {
background-color: red;
padding: 0 5px 0 10px;
}
.b {
background-color: green;
padding: 0 10px 0 10px;
flex: 2;
}
.c {
background-color: lightgreen;
padding: 0 10px 0 15px;
flex: 3;
}
.d {
background-color: lightblue;
padding: 0 15px 0 15px;
flex: 3;
}
.a.mod {
flex-basis: 15px;
}
.b.mod {
flex-basis: 20px;
}
.c.mod {
flex-basis: 25px
}
.d.mod {
flex-basis: 30px;
}
<h2>flex-basis:0 for all flex-items</h2>
<div>
<div class="a"></div>
<div class="b"></div>
<div class="c"></div>
<div class="d"></div>
</div>
<br><br>
<h2>flex-basis = padding_left + padding_right on all flex-items</h2>
<div>
<div class="a mod"></div>
<div class="b mod"></div>
<div class="c mod"></div>
<div class="d mod"></div>
</div>
As you can see both flex-basis: 0
and flex-basis: padding_left + padding_right
are equivalent. Not only that any value of flex-basis
< padding_left + padding_right
will be taken as equivalent to flex-basis
= padding_left + padding_right
. As to why does this behaviour occur, I think it is some sort of inconsistency/bug as suggested by w3.org here(Reference1 again). In other words box-sizing: border-box
is ignored.
Upvotes: 2
Reputation: 115295
That's the correct behaviour as far as I am aware.
flex:1
is, of course, shorthand for:
flex-grow:1;
flex-shrink:1;
flex-basis:0
This allows the div to grow if necessary which, in this case, it does. It's not automatically going to maintain the flex-items as all the same size if they are, in fact, different.
Upvotes: 2
Reputation: 372029
A flex item's size with padding and flex-grow
is determined by calculations in the flexbox spec.
These calculations are similar to the sizing of flex items with padding and flex-shrink
.
Frankly, the math is quite technical and not the easiest thing in the world to understand.
But if you want to get into it, here are the details:
Below are examples that hopefully make the behavior more clear.
NOTE: Keep in mind that flex-grow
is not a tool for directly establishing the length of a flex item. It's a tool for distributing space in the container among flex items. The flex-basis
property sets the initial main size of a flex item. If flex-grow
is used with flex-basis
, the problem in the question is resolved (see example #4 below).
Example #1
In a block container, where you have box-sizing: border-box
, the boxes in your code will render evenly regardless of padding.
body > div {
height: 50px;
/* display: flex; */
font-size: 0; /* remove inline block whitespace */
}
body > div > div {
/* flex: 1; */
box-sizing: border-box;
height: 50px;
display: inline-block;
width: 50%;
}
#a {
background-color: red;
}
#b {
background-color: green;
}
#c {
padding: 10px;
background-color: blue;
}
#d {
background-color: yellow;
<div>
<div id="a"></div>
<div id="b"></div>
</div>
<div>
<div id="c"></div>
<div id="d"></div>
</div>
Example #2
In a flex container, where you have box-sizing: border-box
, and the width
or flex-basis
is used to calculate length, the boxes will render evenly regardless of padding.
body > div {
height: 50px;
display: flex;
}
body > div > div {
flex-basis: 50%;
/* width: 50%; this works, as well */
box-sizing: border-box;
}
#a {
background-color: red;
}
#b {
background-color: green;
}
#c {
padding: 10px;
background-color: blue;
}
#d {
background-color: yellow;
<div>
<div id="a"></div>
<div id="b"></div>
</div>
<div>
<div id="c"></div>
<div id="d"></div>
</div>
Example #3
In a flex container, where you have box-sizing: border-box
and flex-grow
, it will appear that box-sizing
doesn't work...
body > div {
height: 50px;
display: flex;
}
body > div > div {
flex: 1;
/* flex-basis: 50%; */
/* width: 50%; this works, as well */
box-sizing: border-box;
}
#a {
background-color: red;
}
#b {
background-color: green;
}
#c {
padding: 10px;
background-color: blue;
}
#d {
background-color: yellow;
<div>
<div id="a"></div>
<div id="b"></div>
</div>
<div>
<div id="c"></div>
<div id="d"></div>
</div>
but that's not really correct...
Example #4
flex-grow
expands the width of a flex item based on available space in the flex container. In other words, it ignores padding (and borders).
However, if you simply specify flex-grow
along with flex-basis
, the border-box
will work:
flex: 1 1 50%; /* flex-grow, flex-shrink, flex-basis */
body > div {
height: 50px;
display: flex;
}
body > div > div {
flex: 1 1 50%; /* flex-grow, flex-shrink, flex-basis */
/* flex-basis: 50%; */
/* width: 50%; this works, as well */
box-sizing: border-box;
}
#a {
background-color: red;
}
#b {
background-color: green;
}
#c {
padding: 10px;
background-color: blue;
}
#d {
background-color: yellow;
<div>
<div id="a"></div>
<div id="b"></div>
</div>
<div>
<div id="c"></div>
<div id="d"></div>
</div>
Upvotes: 41