Yair
Yair

Reputation: 518

Why is padding expanding a flex item?

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

Answers (5)

Alon Rosenfeld
Alon Rosenfeld

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

Temani Afif
Temani Afif

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

user31782
user31782

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

Paulie_D
Paulie_D

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

Michael Benjamin
Michael Benjamin

Reputation: 372029

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

Upvotes: 41

Related Questions