Flex items not respecting margins and box-sizing: border-box
Keep in mind that box-sizing: border-box
brings padding and borders into the width / height calculation, but not margins. Margins are always calculated separately.
The box-sizing
property takes two values:
content-box
border-box
It does not offer padding-box
or margin-box
.
Consider those terms when referring to the CSS Box Model.
source: W3C
3.1. Changing the Box Model: the
box-sizing
property
content-box
The specified width and height apply to the width and height respectively of the content box of the element. The padding and border of the element are laid out and drawn outside the specified width and height.
border-box
Length and percentages values for width and height on this element determine the border box of the element. That is, any padding or border specified on the element is laid out and drawn inside this specified width and height. The content width and height are calculated by subtracting the border and padding widths of the respective sides from the specified width and height properties.
Also, an initial setting of a flex container is flex-shrink: 1
. This means that flex items can shrink in order to fit within the container.
Therefore, a specified width
, height
or flex-basis
will not hold, unless flex-shrink
is disabled.
You can override the default with flex-shrink: 0
.
Here's a more complete explanation: What are the differences between flex-basis and width?
Here's a simple solution:
You have four boxes. You want three on row 1 and the last on row 2.
This is what you have:
flex: 1 1 33.33%;
margin: 10px;
This breaks down to:
flex-grow: 1
flex-shrink: 1
flex-basis: 33.33%
We know that box-sizing: border-box
factors padding and borders into the flex-basis
. That's not a problem. But what about the margins?
Well, since you have flex-grow: 1
on each item, there is no need for flex-basis
to be 33.33%.
Since flex-grow
will consume any free space on the row, flex-basis
only needs to be large enough to enforce a wrap.
Since margins also consume free space, flex-grow
will not intrude into the margin space.
So try this instead:
flex: 1 1 26%;
margin: 10px;
* {
box-sizing: border-box;
}
.horizontal-layout {
display: flex;
width: 400px;
}
header > span {
flex: 1 0 26%; /* ADJUSTED */
margin: 10px;
}
header#with-border-padding {
flex-wrap: wrap;
}
header#with-border-padding>span {
flex: 1 0 26%; /* ADJUSTED */
}
header#with-border-padding>.button {
border: 1px solid black;
padding-left: 5px;
}
header>.button {
background-color: grey;
}
header>.app-name {
background-color: orange;
}
NO flex-wrap: wrap, so it not respects the flex 33% <br/>
<header class="horizontal-layout">
<span class="button">A</span>
<span class="app-name">B</span>
<span class="button">C</span>
<span class="button">D</span>
</header>
<br/><br/> WITH flex-wrap: wrap : I expect to have 3 boxes in first row and D box in a down<br/>
<header id="with-border-padding" class="horizontal-layout">
<span class="button">A</span>
<span class="app-name">B</span>
<span class="button">C</span>
<span class="button">D</span>
</header>
As well pointed out in Michael_B's answer margins are not taken into account when calculating flex item width. But you can subtract margin
s from flex-basis
to get desired behaviour.
For current case:
header > span {
/* 33.33% - margin-left - margin-right */
flex: 1 1 calc(33.33% - 20px);
margin: 10px;
}
Also you should set box-sizing: border-box
to include padding (and borders) when calculating width
.
* {
box-sizing: border-box;
}
.horizontal-layout {
display: flex;
width: 400px;
}
header > span {
flex: 1 0 calc(33.33% - 20px);
margin: 10px;
}
header#with-border-padding {
flex-wrap: wrap;
}
header#with-border-padding > span {
flex: 1 0 calc(33% - 20px);
}
header#with-border-padding > .button {
border: 1px solid black;
padding-left: 5px;
}
header > .button {
background-color: grey;
}
header > .app-name {
background-color: orange;
}
NO flex-wrap: wrap, so it not respects the flex 33% <br/>
<header class="horizontal-layout">
<span class="button">A</span>
<span class="app-name">B</span>
<span class="button">C</span>
<span class="button">D</span>
</header>
<br/><br/> WITH flex-wrap: wrap : I expect to have 3 boxes in first row and D box in a down<br/>
<header id="with-border-padding" class="horizontal-layout">
<span class="button">A</span>
<span class="app-name">B</span>
<span class="button">C</span>
<span class="button">D</span>
</header>