Why is padding expanding a flex item?
Asked Answered
V

5

38

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>
Vidal answered 15/10, 2015 at 9:48 Comment(1)
Late to the game but I've run into the same issue (which I'm sure works according to the documentation) - try using explicitly flex: 50% because it is actually a different behaviour than flex: 1Incorruption
E
41

The calculations are defined in the spec.

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:


Examples

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>

jsFiddle demo


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>

jsFiddle demo


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>

jsFiddle demo

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>

jsFiddle demo

Elsi answered 15/10, 2015 at 12:20 Comment(1)
The "fix" simply sets box width to 50%, which will break if you add margins or anything else that causes flex-grow or shrink to have effect again.Sharpshooter
C
3

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.

Cosmopolite answered 25/8, 2021 at 20:19 Comment(8)
box-sizing has nothing to do here, I guess, since we have no explicit width set. Also I don't think this is a bug. I will add an answer when I get all the informationCaught
@TemaniAfif What I mean by box-sizing: border-box being ignored is that the expected behaviour should be to not include padding and border in the hypothetical main size, which would make HMS 0 when flex basis is 0 and then by respecting the box-sizing both the flex-items will get same total width(flex-grow being 1) regardless of the padding and border size.Cosmopolite
no, box-sizing was never meant to work that way. We never exclude padding but we include padding into the calculation. Padding is always considered into the calculation, the only difference is that if you set a width W that width will include the padding ONLY if that width is bigger than the specified padding. I think you are misunderstanding box-sizing here. Ignoring padding like you are thinking can lead us to negative sizing and this is not logicalCaught
@TemaniAfif How can there be negative sizing? Does the spec assume HMS = content + padding, in its algorithms, making content negative for HMS being zero?Cosmopolite
forget about flexbox and consider width. Do you agree that if you make your element width:0 and padding:10px then the final width will end 20px? and this whatever the box-sizing. The final width cannot be 0 because to get 0 we have to negate the padding so we have to add some virtual negative spacing to reach 0 (20px + -20px). Only margin accept negative values. content, padding, border cannot be negativeCaught
@TemaniAfif What you are saying is true for physical width or css width. But, I am only considering this in the context of flex-basis. flex-basis != (content + padding). My point is using HMS = flex-basis is far better implementation than using HMS = css width(if flex-basis < css width).Cosmopolite
no, it's not better. It will lead to negative sizing as I said. Imagine a container having a width equal to 100px and two elements inside it. One with a padding 40px and another with 60px. Following your logic, if we set flex:1 to both we should end with 50px for both which is not a correct behavior. The second one will either have a negative sizing or a computed value of padding different from the one specified and in both cases it's not logical (same thing with borders). If I set a padding or a border, I expect to get them and not to ignore themCaught
@TemaniAfif You last comment makes sense. In this situation , assuming my logic, flex-grow is actually shrinking things down, which could be circumvented by not applying flex-grow at all in these situations(as flex-grow is supposed grow when there's available space). I see where's this heading... w3c is obviously smart in choosing there algorithms :-}Cosmopolite
C
3

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>
Caught answered 27/8, 2021 at 22:32 Comment(2)
Did they always have this concept of hypothetical main size? A lot of people on github thought of it as a bug back in 2016.Cosmopolite
@Cosmopolite as far as I know yes, here is an old version of the spec: w3.org/TR/2015/WD-css-flexbox-1-20150514 (you will find what I am explaining there). The bug you are refering is not relevant here because we are setting 0 as value. Take an element with 10px of padding and a width equal to 0 and you will see the final width equal to 10px whatever the box-sizing. box-sizing enter in play when the value of width (or flex-basis) is big enough to include the padding. Take my last example and toggle the box-sizing and you will see a differenceCaught
D
2

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.

Dottydoty answered 15/10, 2015 at 10:3 Comment(1)
This is not the correct behaviour, please have a look at my answer below.Cosmopolite
C
0

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;
  }
Chaperone answered 27/7, 2023 at 12:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.